Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using SentryAssetBundle() causes asset images to flash when CupertinoContextMenu() tapped #2528

Closed
kouroku-room opened this issue Dec 20, 2024 · 7 comments · Fixed by #2577
Closed
Assignees

Comments

@kouroku-room
Copy link

kouroku-room commented Dec 20, 2024

Platform

Flutter Mobile Android

Obfuscation

Disabled

Debug Info

Disabled

Doctor

flutter doctor -v
[√] Flutter (Channel stable, 3.27.0, on Microsoft Windows [Version 10.0.22631.4602], locale ja-JP)
• Flutter version 3.27.0 on channel stable at C:\dev\flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 8495dee1fd (9 days ago), 2024-12-10 14:23:39 -0800
• Engine revision 83bacfc525
• Dart version 3.6.0
• DevTools version 2.40.2

[√] Windows Version (Installed version of Windows is version 10 or higher)

[√] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
• Android SDK at C:\Users\sugur\AppData\Local\Android\sdk
• Platform android-35, build-tools 35.0.0
• Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java
• Java version OpenJDK Runtime Environment (build 21.0.3+-12282718-b509.11)
• All Android licenses accepted.

[√] Chrome - develop for the web
• Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe

[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.10.1)
• Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community
• Visual Studio Community 2022 version 17.10.34928.147
• Windows 10 SDK version 10.0.22621.0

[√] Android Studio (version 2024.1)
• Android Studio at C:\Program Files\Android\Android Studio Koala Feature Drop
• Flutter plugin can be installed from:
https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
https://plugins.jetbrains.com/plugin/6351-dart
• Java version openjdk version "17.0.11" 2024-04-16

[√] Android Studio (version 2024.2)
• Android Studio at C:\Program Files\Android\Android Studio
• Flutter plugin can be installed from:
https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
https://plugins.jetbrains.com/plugin/6351-dart
• Java version openjdk version "21.0.3" 2024-04-16

[√] VS Code (version 1.95.3)
• VS Code at C:\Users\sugur\AppData\Local\Programs\Microsoft VS Code
• Flutter extension version 3.102.0

[√] Connected device (4 available)
• SH M25 (mobile) • adb-SX3LHMB420105423-rrDg4j._adb-tls-connect._tcp • android-arm64 • Android 14 (API 34)
• Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.22631.4602]
• Chrome (web) • chrome • web-javascript • Google Chrome 131.0.6778.140
• Edge (web) • edge • web-javascript • Microsoft Edge 131.0.2903.99

[√] Network resources
• All expected network resources are available.

• No issues found!

Version

8.11.1

Steps to Reproduce

Probably the same thing is occurring with that issue.
#1457

The above issue is closed because they could not provide reproducible code, but I could create it and will provide it.
https://github.com/kouroku-room/sample_for_issue/tree/9ca1d8f7fc4c7287cb7a96e0d9b4b63e698f6635

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        useMaterial3: true,
      ),
      home: DefaultAssetBundle(
        bundle: SentryAssetBundle(),
        child: MyHomePage(),
      ),
      // No flickering occurs when SentryAssetBundle() is not used as shown below.
      // home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      body: CupertinoContextMenu(
        actions: [
          CupertinoContextMenuAction(
            child: const Text('Action 1'),
            onPressed: () {
              Navigator.of(context).pop();
            },
          ),
        ],
        child: Image.asset(
          'assets/bottle_1.png',
          width: 500,
          height: 500,
          // No flickering occurs when setting scale as shown below.
          // scale: 1,
          fit: BoxFit.cover,
        ),
      ),
    );
  }
}
  1. Prepare Assets files with different resolutions.
    https://docs.flutter.dev/ui/assets/assets-and-images#resolution-aware

  2. Put CupertinoContextMenu(child: Image.asset()).
    ->At this time, a long press will not cause flickering.

  3. When wrapped in DefaultAssetBundle() with SentryAssetBundle() in place, the image flickers when long pressed.

Expected Result

No image flicker shall occur.

Actual Result

The image flickers when long pressed CupertinoContextMenu().

Are you willing to submit a PR?

None

@buenaflor
Copy link
Contributor

hey, thanks for the issue

does this happen consistently?

@buenaflor
Copy link
Contributor

I'm able to reproduce this, thanks for the snippet

@buenaflor buenaflor moved this from Needs Discussion to Backlog in Mobile & Cross Platform SDK Dec 20, 2024
@kouroku-room
Copy link
Author

does this happen consistently?

I tried it on a device called "SH M25 (mobile)" and it occurred 100% of the time.

@getsantry getsantry bot moved this to Waiting for: Product Owner in GitHub Issues with 👀 3 Dec 21, 2024
@buenaflor
Copy link
Contributor

thanks for the info, we're looking into it

@denrase
Copy link
Collaborator

denrase commented Jan 14, 2025

AssetBundle Flickering

I could reproduce the issue with the code that @kouroku-room provided. I tried to narrow the issue down and implemented some minimal classes of AssetBundle and did some test runs to see when it occurs.

Tests

Root AssetBundle

Using just the root bundle is fine, we do not experience any flickering

DefaultAssetBundle(
    bundle: rootBundle,
    child: MyHomePage(),
)

Delegating AssetBundle

Implement a class which wraps rootBundle and just delegates calls to it, without anything beeing awaited. We get flickering on the first tap, and after that no more flickering. The subsequent calls are all cached, as the root bundle implementation is a subclass of CachingAssetBundle.

DefaultAssetBundle(
    bundle: DelegatingAssetBundle(rootBundle),
    child: MyHomePage(),
)

class DelegatingAssetBundle implements AssetBundle {

  final AssetBundle _bundle;

  DelegatingAssetBundle(this._bundle);

  @override
  Future<ByteData> load(String key) {
    return _bundle.load(key);
  }

  @override
  Future<ImmutableBuffer> loadBuffer(String key) {
    return _bundle.loadBuffer(key);
  }

  @override
  Future<String> loadString(String key, {bool cache = true}) {
    return _bundle.loadString(key, cache: cache);
  }

  @override
  Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) {
    return _bundle.loadStructuredBinaryData(key, parser);
  }

  @override
  Future<T> loadStructuredData<T>(String key, Future<T> Function(String value) parser) {
    return _bundle.loadStructuredData(key, parser);
  }

  @override
  void evict(String key) {
    _bundle.evict(key);
  }

  @override
  void clear() {
    _bundle.clear();
  }
}

Tracing AssetBundle

If we wart the calls and await them, for example to do tracing, we always get flickering. This is basically what we do in SentryAssetBundle.

DefaultAssetBundle(
    bundle: TracingAssetBundle(rootBundle),
    child: MyHomePage(),
)

class TracingAssetBundle implements AssetBundle {

  final AssetBundle _bundle;

  TracingAssetBundle(this._bundle);

  @override
  Future<ByteData> load(String key) {
    return traceAction("load", () => _bundle.load(key));
  }

  @override
  Future<ImmutableBuffer> loadBuffer(String key) {
    return traceAction("loadBuffer", () =>_bundle.loadBuffer(key));
  }

  @override
  Future<String> loadString(String key, {bool cache = true}) {
    return traceAction("loadString", () => _bundle.loadString(key, cache: cache));
  }

  @override
  Future<T> loadStructuredData<T>(String key, Future<T> Function(String value) parser) {
    return traceAction("loadStructuredData", () => _bundle.loadStructuredData(key, parser));
  }

  @override
  Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) {
    return traceAction("loadStructuredBinaryData", () => _bundle.loadStructuredBinaryData(key, parser));
  }

  @override
  void evict(String key) {
    traceVoidAction('evict', () => _bundle.evict(key));
  }

  @override
  void clear() {
    traceVoidAction('clear', () => _bundle.clear());
  }

  // Helper

  Future<T> traceAction<T>(String traceName, Future<T> Function() action) {
    return Future<T>(() async {
      print("start trace $traceName");
      final data = await action();
      print("end trace $traceName");
      return data;
    });
  }

  FutureOr<void> traceVoidAction(String traceName, FutureOr<void> Function() action) {
    return Future<void>(() async {
      print("start trace $traceName");
      final data = await action();
      print("end trace $traceName");
      return data;
    });
  }
}

Conclusion

I'm not sure yet how to avoid the flickering, as even the most simple examples produce it. I also don't fully understand yet how just wrapping the root bundle and delegating the calls, without awaiting, is provoking one flicker.

The rootBundle is a PlatformAssetBundle which in turn subclasses CachingAssetBundle.

@denrase
Copy link
Collaborator

denrase commented Jan 14, 2025

Ok, resolved the first issue of DelegatingAssetBundle where we had one flicker. When loading images, we also have to provide it the custom Asset bundle using bundle: DefaultAssetBundle.of(context).

Image.asset(
  'assets/bottle_1.png',
  bundle: DefaultAssetBundle.of(context),
  width: 500,
  height: 500,
  // No flickering occurs when setting scale as shown below.
  // scale: 1,
  fit: BoxFit.cover,
)

@denrase
Copy link
Collaborator

denrase commented Jan 14, 2025

Found a solution. 🥳

When wrapping the methods, we need to use a completer and find out through control-flow if we are async or sync (cached), that way we do not have the flickering.

class TracingAssetBundle implements AssetBundle {

  final AssetBundle _bundle;

  TracingAssetBundle(this._bundle);

  @override
  Future<ByteData> load(String key) {
    return traceAction("load", _bundle.load(key));
  }

  @override
  Future<ImmutableBuffer> loadBuffer(String key) {
    return traceAction("loadBuffer", _bundle.loadBuffer(key));
  }

  @override
  Future<String> loadString(String key, {bool cache = true}) {
    return traceAction("loadString", _bundle.loadString(key, cache: cache));
  }

  @override
  Future<T> loadStructuredData<T>(String key, Future<T> Function(String value) parser) {
    return traceAction("loadStructuredData", _bundle.loadStructuredData(key, parser));
  }

  @override
  Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) {
    return traceAction("loadStructuredBinaryData", _bundle.loadStructuredBinaryData(key, parser));
  }

  @override
  void evict(String key) {
    _bundle.evict(key);
  }

  @override
  void clear() {
    _bundle.clear();
  }

  // Helper

  Future<T> traceAction<T>(String traceName, Future<T> future) {
    Completer<T>? completer;
    Future<T>? result;

    print("start trace $traceName");

    future.then((data) {

      print("end trace $traceName");

      if (completer != null) {
        completer.complete(data);
      } else {
        result = SynchronousFuture<T>(data);
      }
    }).onError((Object error, StackTrace stack) {
      completer?.completeError(error, stack);
    });
    if (result != null) {
      return result!;
    }
    completer = Completer<T>();
    return completer.future;
  }
}

Will create a PR that applies this approach to SentryAssetBundle

@denrase denrase moved this from Backlog to In Progress in Mobile & Cross Platform SDK Jan 14, 2025
@buenaflor buenaflor moved this from In Progress to Needs Review in Mobile & Cross Platform SDK Jan 22, 2025
@github-project-automation github-project-automation bot moved this from Needs Review to Done in Mobile & Cross Platform SDK Jan 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Archived in project
Status: Done
Development

Successfully merging a pull request may close this issue.

3 participants