samedi 24 décembre 2022

Flutter release version only bug on Android

Description

I've developed a dart class to generate random circles for the splash screen of my app. It generates circles of different sizes that do not intersect each other and also leave some space in the center of the screen for the logo. This code works fine on IOS and in debug mode on Android. However, when run in release mode on Android - there's a white screen instead of the expected SplashScreen. The code for reproduction is at the bottom of this issue.

Expected result (might differ a bit - it's random)

Simulator Screen Shot - iPhone 14 Pro - 2022-12-24 at 10 20 36

Steps to Reproduce

  1. Try running with flutter run --debug on Android device - works just fine, the SplashScreen renders
  2. Try running with flutter run --release on Android device - white screen instead of expected SplashScreen, nothing happens
  3. Comment out the lines inside // REMOVE TO AVOID THE BUG.
  4. Try running with flutter run --debug on Android device - works just fine
  5. Try running with flutter run --release on Android device - works just fine

Works fine on IOS in all modes without commenting out the buggy lines.

Flutter commands outputs

flutter run --verbose --release:

[        ] Installing APK.
[   +1 ms] Installing build/app/outputs/flutter-apk/app.apk...
[        ] executing: /Users/olegbezr/Library/Android/sdk/platform-tools/adb -s f213fa8d install -t -r /Users/olegbezr/Code/splash_bug/build/app/outputs/flutter-apk/app.apk
[+1338 ms] Performing Streamed Install
           Success
[   +3 ms] Installing build/app/outputs/flutter-apk/app.apk... (completed in 1,339ms)
[   +2 ms] executing: /Users/olegbezr/Library/Android/sdk/platform-tools/adb -s f213fa8d shell echo -n 8797e6dd9572aee4f95fbb041f30bfdedcd623cb > /data/local/tmp/sky.com.example.splash_bug.sha1
[  +45 ms] executing: /Users/olegbezr/Library/Android/sdk/platform-tools/adb -s f213fa8d shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -f 0x20000000 --ez enable-dart-profiling true com.example.splash_bug/com.example.splash_bug.MainActivity
[  +87 ms] Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x20000000 cmp=com.example.splash_bug/.MainActivity (has extras) }
[   +1 ms] Application running.
[   +1 ms] Flutter run key commands.
[        ] h List all available interactive commands.
[        ] c Clear the screen
[        ] q Quit (terminate the application on the device).

flutter analyse:

Analyzing splash_bug...                                         
No issues found! (ran in 2.0s)

flutter doctor -v:

[✓] Flutter (Channel stable, 3.3.8, on macOS 13.0 22A380 darwin-arm, locale en-US)
    • Flutter version 3.3.8 on channel stable at /Applications/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 52b3dc25f6 (6 weeks ago), 2022-11-09 12:09:26 +0800
    • Engine revision 857bd6b74c
    • Dart version 2.18.4
    • DevTools version 2.15.0

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
    • Android SDK at /Users/olegbezr/Library/Android/sdk
    • Platform android-33, build-tools 33.0.0
    • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 14.2)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 14C18
    • CocoaPods version 1.11.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio
    • Android Studio at /Applications/Android Studio Preview.app/Contents
    • 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 Runtime Environment (build 11.0.12+0-b1504.28-7817840)

[✓] Android Studio (version 2021.3)
    • Android Studio at /Applications/Android Studio.app/Contents
    • 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 Runtime Environment (build 11.0.13+0-b1751.21-8125866)

[✓] IntelliJ IDEA Ultimate Edition (version 2021.2.2)
    • IntelliJ at /Applications/IntelliJ IDEA.app
    • 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

[✓] VS Code (version 1.74.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.56.0

[✓] Connected device (4 available)
    • IN2020 (mobile)        • f213fa8d                             • android-arm64  • Android 11 (API 30)
    • iPhone 14 Pro (mobile) • 84586B2B-4DD8-4682-941D-6E790AF5CCAC • ios            • com.apple.CoreSimulator.SimRuntime.iOS-16-2 (simulator)
    • macOS (desktop)        • macos                                • darwin-arm64   • macOS 13.0 22A380 darwin-arm
    • Chrome (web)           • chrome                               • web-javascript • Google Chrome 108.0.5359.124

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!

The code

import 'dart:math';

import 'package:flutter/material.dart';

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Demo',
      home: SplashScreen(),
    );
  }
}

class SplashScreen extends StatefulWidget {
  const SplashScreen({Key? key}) : super(key: key);

  @override
  State<SplashScreen> createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen> {
  List<SplashRandomCircle> _circles = [];

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final width = MediaQuery.of(context).size.width;
    final height = MediaQuery.of(context).size.height;
    final circlesCount = 8 + Random().nextInt(4);

    setState(() {
      _circles = SplashRandomCircle.randomListNoIntersection(
        count: circlesCount,
        width: width,
        height: height,
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          ..._circles
              .map(
                (e) => Positioned(
                  left: e.xCoord - e.radius,
                  top: e.yCoord - e.radius,
                  child: Container(
                    decoration: BoxDecoration(
                      shape: BoxShape.rectangle,
                      borderRadius: BorderRadius.circular(e.radius * 0.9),
                      color: e.color,
                    ),
                    width: e.radius * 2,
                    height: e.radius * 2,
                  ),
                ),
              )
              .toList(),
        ],
      ),
    );
  }
}

class SplashRandomCircle {
  static int opacityPrecision = 10000;
  static Random randomizer = Random();
  static Color mainColor = const Color.fromARGB(255, 156, 74, 67);

  static Color randomBlue(double minOpacity, double maxOpacity) {
    final deltaOpacity = ((maxOpacity - minOpacity) * opacityPrecision).round();
    return mainColor.withOpacity(
      randomizer.nextInt(deltaOpacity) / opacityPrecision + minOpacity,
    );
  }

  static double randomX(double width, double multiplier) {
    final right = randomizer.nextBool();

    var xCoord = randomizer.nextDouble() * width * multiplier;
    if (right) {
      xCoord = width - xCoord;
    }
    return xCoord;
  }

  static double randomY(double height, double multiplier) {
    final bottom = randomizer.nextBool();

    var yCoord = randomizer.nextDouble() * height * multiplier;
    if (bottom) {
      yCoord = height - yCoord;
    }
    return yCoord;
  }

  static double randomRadius(double minRadius, double width, double multiplier) {
    return randomizer.nextDouble() * width * multiplier + minRadius;
  }

  final double xCoord;
  final double yCoord;
  final double radius;
  final Color color;

  SplashRandomCircle({
    required this.xCoord,
    required this.yCoord,
    required this.radius,
    required this.color,
  });

  static SplashRandomCircle random({
    required double width,
    required double height,
    double widthMultiplier = 0.5,
    double heightMultiplier = 0.5,
    double minRadius = 10,
    double radiusMultiplier = 0.5,
    double minOpacity = 0.3,
    double maxOpacity = 0.8,
  }) {
    return SplashRandomCircle(
      xCoord: randomX(width, widthMultiplier),
      yCoord: randomY(height, heightMultiplier),
      radius: randomRadius(minRadius, width, radiusMultiplier),
      color: randomBlue(minOpacity, maxOpacity),
    );
  }

  static List<SplashRandomCircle> randomListNoIntersection({
    required int count,
    required double width,
    required double height,
    double widthMultiplier = 0.6,
    double heightMultiplier = 0.6,
    double minRadius = 6,
    double radiusMultiplier = 0.6,
    double minOpacity = 0.4,
    double maxOpacity = 0.9,
    double logoWidthMultiplier = 0.4,
    double circlesSpacingMultiplier = 0.04,
  }) {
    final circles = <SplashRandomCircle>[];
    while (circles.length < count) {
      final newCircle = SplashRandomCircle.random(
        width: width,
        height: height,
        widthMultiplier: widthMultiplier,
        heightMultiplier: heightMultiplier,
        minRadius: minRadius,
        radiusMultiplier: radiusMultiplier,
        minOpacity: minOpacity,
        maxOpacity: maxOpacity,
      );

      var checkPosition = true;
      // check if the new circle intersects with any of the existing circles
      for (final circle in circles) {
        final xDiff = circle.xCoord - newCircle.xCoord;
        final yDiff = circle.yCoord - newCircle.yCoord;
        final distance = sqrt(xDiff * xDiff + yDiff * yDiff);

        // REMOVE TO AVOID THE BUG
        if (distance < circle.radius + newCircle.radius + width * circlesSpacingMultiplier) {
          checkPosition = false;
          break;
        }
        // REMOVE TO AVOID THE BUG
      }

      // check if the new circle doesn't intersect with the logo
      final centerXDiff = width / 2 - newCircle.xCoord;
      final centerYDiff = height / 2 - newCircle.yCoord;
      final distance = sqrt(centerXDiff * centerXDiff + centerYDiff * centerYDiff);

      // REMOVE TO AVOID THE BUG
      if (distance < newCircle.radius + width * logoWidthMultiplier) {
        checkPosition = false;
      }
      // REMOVE TO AVOID THE BUG

      if (checkPosition) {
        circles.add(newCircle);
      }
    }
    return circles;
  }
}



Aucun commentaire:

Enregistrer un commentaire