funwithflutter / flutter_confetti

A Flutter widget that shoots confetti all over the screen.
https://pub.dev/packages/confetti
MIT License
439 stars 79 forks source link

Not shown in release mode #3

Open hanielbaez opened 5 years ago

hanielbaez commented 5 years ago

As the title says, I don't get the effect at release mode, but during debug mode, it works ok. My code is pretty much the same as the example, in my case a use provider to use the ConfettiControlle deep in the widget tree. It goes like this.

main.dart

ListenableProvider<ConfettiController>.value(
              value: controllerTopCenter,
              child: HomePage(),
            ),

video.dart

//Flutter and Dart import
import 'dart:async';
import 'package:confetti/confetti.dart';
import 'package:flutter/material.dart';
import 'package:flutter_icons/simple_line_icons.dart';

//Self import
import 'package:Tekel/core/model/riddle.dart';
import 'package:Tekel/core/viewModel/videoViewModel.dart';
import 'package:provider/provider.dart';

class VideoLayaout extends StatefulWidget {
  final Riddle riddle;
  final VideoViewModel model;
  final Stream shouldTriggerChange;

  VideoLayaout({this.riddle, this.model, this.shouldTriggerChange});

  @override
  _VideoLayaoutState createState() => _VideoLayaoutState();
}

class _VideoLayaoutState extends State<VideoLayaout>
    with TickerProviderStateMixin {
  StreamSubscription streamSubscription;
  AnimationController fadeController;
  Animation fadeAnimation;

  @override
  initState() {
    super.initState();
    streamSubscription =
        widget.shouldTriggerChange.listen((value) => success(value));
    fadeController =
        AnimationController(vsync: this, duration: Duration(seconds: 1));
    fadeAnimation = Tween(begin: 0.0, end: 1.0).animate(fadeController);
  }

  @override
  void dispose() {
    widget.model.videoController?.dispose();
    streamSubscription.cancel();
    fadeController.dispose();
    super.dispose();
  }

  void success(value) async {
    if (value == true) {
      Future.delayed(
        Duration.zero,
        () => setState(
          () {
            fadeController.forward();
          },
        ),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Stack(
        fit: StackFit.loose,
        children: <Widget>[
          FutureBuilder(
            future: widget.model.getMedia(),
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              switch (snapshot.connectionState) {
                case ConnectionState.none:
                  return Text('Check your network connection.');
                case ConnectionState.active:
                case ConnectionState.waiting:
                case ConnectionState.done:
                  if (snapshot.hasError)
                    return Text('Error: try later, please');
                  return Padding(
                    padding: const EdgeInsets.all(10.0),
                    child: Center(child: widget.model.widget),
                  );
              }
              return Text('Unreachable.');
            },
          ),
          FadeTransition(
              opacity: fadeAnimation, child: buildSuccessContainer()),
        ],
      ),
    );
  }

  Container buildSuccessContainer() {
    fadeController.isAnimating
        ? Provider.of<ConfettiController>(context).play()
        : null;
    return Container(
      padding: EdgeInsets.all(10.0),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: [Colors.yellow[600], Colors.orange[400]],
          begin: const FractionalOffset(0.0, 0.0),
          end: const FractionalOffset(1, 0.0),
          stops: [0.0, 1.0],
          tileMode: TileMode.clamp,
        ),
      ),
      child: Center(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Icon(
              SimpleLineIcons.getIconData('check'),
              color: Colors.black,
              size: 40.0,
            ),
            SizedBox(
              height: 20.0,
            ),
            Text('Success',
                style: TextStyle(color: Colors.black, fontSize: 40.0)),
          ],
        ),
      ),
    );
  }
}

Beforehand, thanks for your time.

funwithflutter commented 5 years ago

I did a quick test and on my side the package is working in release mode. I don't believe that is the issue.

Other things that might be happening are:

hanielbaez commented 5 years ago

I was doing some manual test at release mode, I found out that the controller is playing, particle sometimes appears, and then almost immediately disappear at the top center.

If triggers the confetti controller, do a Navigator.pushReplacementNamed after that come back to the previous page, confetti starts to play.

I think that for some reason the screen is not been updating to let the controller play.

I do not get any error logs.

void main() {
  setPortraitOrientation();
  return runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  ConfettiController controllerTopCenter;

  @override
  void initState() {
    controllerTopCenter = ConfettiController(duration: Duration(seconds: 10));
    super.initState();
  }

  @override
  void dispose() {
    controllerTopCenter.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: providers,
      child: MaterialApp(
        title: 'Tekel',
        theme: costumTheme,
        home: Stack(
          children: <Widget>[
            ListenableProvider<ConfettiController>.value(
              value: controllerTopCenter,
              child: HomePage(),
            ),
            Align(
              alignment: Alignment.topCenter,
              child: ConfettiWidget(
                confettiController: controllerTopCenter,
                blastDirection: pi / 2,
                maxBlastForce: 10,
                minBlastForce: 2,
                emissionFrequency: 0.05,
                numberOfParticles: 5,
              ),
            ),
          ],
        ),
        onGenerateRoute: router.generateRoute,
        initialRoute: '/',
      ),
    );
  }
}

UPDATE: I added the confetti Controller.play () to my stream.listen (), and now it works perfectly fine. I just don't clary understand with my last implementation work at debug mode. Anyway thanks for your time.

funwithflutter commented 5 years ago

Perfect! If you figure it out drop a message. I'm closing this issue for now.

hanielbaez commented 5 years ago

Yes, it is solved. Thanks.

pro100svitlo commented 3 years ago

I am facing mostly the same problem: works fine on debug, mostly not showing on release. On debug it works just as expected. But with the release build, it shows only one blinking confetti in the shooting point.

Other things that might be happening are:

  • Some Assert logic in your code base that only triggers on debug builds that results in the confetti_controller not playing
  • The confetti_controller gets disposed before use (if that is the case there should be error logs)

checked this. looks like not my case. here some part of my code:


  @override
  void initState() {
    _controllerCenter = ConfettiController(duration: const Duration(seconds: 3));
    WidgetsBinding.instance.addPostFrameCallback((_) {
        _controllerCenter.play();   
    });
    super.initState();
  }

  Widget _createIcon() {
    const size = 150.0;
    final stack = Stack(
      children: [
          SvgWidget.asset(_saluteIconPath, width: size, height: size),        
          Center(child: _createConfetti()),
      ],
    );
    return SizedBox(
      width: size,
      height: size,
      child: stack,
    );
  }

  Widget _createConfetti() => ConfettiWidget(
        confettiController: _controllerCenter,
        blastDirection: -pi / 4,
        emissionFrequency: 0.8,
        minimumSize: const Size(10, 3),
        maximumSize: const Size(20, 5),
        numberOfParticles: 2,
        gravity: 0.02,
        maxBlastForce: 3,
        minBlastForce: 1,
      );

will appreciate any ideas. In case of need can add a gif video with an examples.

pro100svitlo commented 3 years ago

@funwithflutter can you please give me any hint/help with my problem?

pro100svitlo commented 3 years ago

some extra details:

funwithflutter commented 3 years ago

@pro100svitlo I've run into times when release mode won't show something while in debug it does show (not related to this package). And normally for me it happens because of a Stack issue. Make sure that the widgets in your stack are defining a size. For example, wrap the widget in Positioned.fill to ensure it takes up all the available space.

You can also try to simplify what you are doing and see if it works under other conditions (for release mode). But it sounds like the confetti is being destroyed as soon as they are created. So it seems like the confetti widget doesn't have enough "space". You can also try wrapping it in a Container with a fixed size and you'll see what I mean. It should destroy the confetti when it leaves that defined "area/space/size"

ateich commented 3 years ago

I'm having a similar issue that only happens when I run a release build on my iPhone. When I open the ConfettiView within showModalBottomSheet, the confetti flashes in the center of the modal instead of expanding outward.

Confetti animates correctly (on device in release build) when

Confetti doesn't animate correctly (on device in release build) when

@funwithflutter Any idea why this happens with on device release builds when _controller.play() is called in initState, but not when _controller.play() is triggered by a button press?

Minimal example:

import 'package:confetti/confetti.dart';
import 'package:flutter/material.dart';

void main() => runApp(const ConfettiSample());

class ConfettiSample extends StatelessWidget {
  const ConfettiSample({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Builder(
        builder: (context) => Scaffold(
          body: Center(
            child: FlatButton(
              child: Text('open confetti in modal view'),
              onPressed: () {
                showModalBottomSheet(
                  context: context,
                  builder: (BuildContext bc) {
                    return ConfettiView();
                  },
                );
              },
            ),
          ),
        ),
      ),
    );
  }
}

class ConfettiView extends StatefulWidget {
  @override
  _ConfettiViewState createState() => _ConfettiViewState();
}

class _ConfettiViewState extends State<ConfettiView> {
  ConfettiController _controller;

  @override
  void initState() {
    _controller = ConfettiController(duration: const Duration(seconds: 3));
    _controller.play(); // <-- This causes the confetti to get stuck in one location and flash (when in a showModalBottomSheet)
    super.initState();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: <Widget>[
          Align(
            alignment: Alignment.center,
            child: ConfettiWidget(
              confettiController: _controller,
              blastDirectionality: BlastDirectionality.explosive,
            ),
          ),
          Align(
            alignment: Alignment.center,
            child: FlatButton(
              onPressed: () => _controller.play(), // <-- This works as expected
              child: Text('blast\nconfetti'),
            ),
          ),
        ],
      ),
    );
  }
}
funwithflutter commented 3 years ago

@ateich I have an idea of what the issue could be. I'll take a look at this next week.

kalebhermes commented 3 years ago

Any chance you had the time to dig into this issue @funwithflutter?

irakalaputenko commented 3 years ago

@funwithflutter what about this issue? No changes?

deep-gaurav commented 3 years ago

@funwithflutter I think issue was with RepaintBoundary in https://github.com/funwithflutter/flutter_confetti/blob/f3fcfb502adeb790395b58eed403ddf6df2fbbe5/lib/src/confetti.dart#L285-L295

I made a fork and removed it https://github.com/deep-gaurav/flutter_confetti . Seems to have fixed it, not sure why though.

benjamincombes commented 2 years ago

I had the same issue too: when working on the single screen using the lib, it worked like a charm. But when testing it in a real-world app, I add all confetti in the same place. I think I might have a clue, however: I use auto_route in my app, and I think it is what causes the problem.

I did find a workaround as if I display my confetti screen:

I wonder if this might not be related to the animation/transition when a new route is displayed? Could it be something similar for other people that got the issue? Thanks and let me know if I can give more information!

amuhaimin02 commented 4 months ago

For me, it is because I used minimum / maximum size based on screen dimension. Partices are not showing at all in release mode. Though, removing minimum/maximumSize or using constant size values (e.g. Size(10, 10)) will fix it.

@override
Widget build(BuildContext context) {
  final confettiSize = MediaQuery.of(context).size.shortestSide * 0.03;
  return Stack(
    children: [
      widget.child,
      Align(
        alignment: const Alignment(0, -0.5),
        child: ConfettiWidget(
          confettiController: _controller,
          blastDirectionality: BlastDirectionality.explosive,
          maxBlastForce: 20,
          minBlastForce: 1,
          emissionFrequency: 0.1,
          numberOfParticles: 50,
          gravity: 0.05,
          minimumSize: Size.square((confettiSize * 0.5).truncateToDouble()), // replace or remove
          maximumSize: Size.square(confettiSize.truncateToDouble()), // replace or remove
        ),
      ),
    ],
  );
}
manuthebyte commented 4 months ago

Hey, I have found a fix! Just change the _isOutsideOfBorder method in the particle.dart file to this:

bool _isOutsideOfBorder(Offset particleLocation) {
    final globalParticlePosition = particleLocation + _particleSystemPosition!;

    return (globalParticlePosition.dy >= _bottomBorder);
  }

This will maybe impact performance very lightly, because now particles only get deleted after they have passed the bottom of the screen, not the left or right.