rrousselGit / riverpod

A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
https://riverpod.dev
MIT License
6.17k stars 943 forks source link

the appropriate way of using animation controllers with riverpod & hooks #209

Closed amqannasa closed 3 years ago

amqannasa commented 3 years ago

Hello, does the following code cause an infinite loop? if so, is there a better place to call timers and status listener? (repeatShake keeps incrementing without stopping on 3 as the condition implies)

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/all.dart';
import 'package:trynkets_app/models/splash_animation.dart';
import 'package:trynkets_app/models/user.dart';
import 'package:trynkets_app/providers/common_providers.dart';
import 'package:trynkets_app/screens/home_screen.dart';
import 'package:trynkets_app/screens/select_language.dart';
import 'package:trynkets_app/widgets/logo_widgets.dart';
import 'package:trynkets_app/widgets/media/trynket_logo.dart';

final splashAnimationProvider = StateProvider((_) => SplashAnimation(
    width: 100,
    alignment: Alignment.center,
    logoWidgets: false,
    flipColor: false,
    repeatShake: 0));

class ShakeCurve extends Curve {
  @override
  double transform(double t) => sin(t * pi * 0.5);
}

class LogoScreen extends HookWidget {
  static const routeName = 'logo-animation';
  static const title = 'Logo Animation';

  @override
  Widget build(BuildContext context) {
    //this will make sure of rules
    useProvider(notificationServiceProvider).createToken();
    final splashAnimation = useProvider(splashAnimationProvider);
    final _trynketLogoController =
        useAnimationController(duration: Duration(milliseconds: 210));
    Timer(Duration(milliseconds: 800), () => _trynketLogoController.reverse);

    final _logoWidgetsController =
        useAnimationController(duration: Duration(milliseconds: 800));
    Timer(Duration(milliseconds: 2300), () => _logoWidgetsController.forward);

    //changing the width
    Timer(
        Duration(milliseconds: 200),
        () =>
            splashAnimation.state = splashAnimation.state.copyWith(width: 200));
    //changing the alignement
    Timer(
        Duration(milliseconds: 1800),
        () => splashAnimation.state =
            splashAnimation.state.copyWith(alignment: Alignment.centerLeft));

    final _curvedAnimation = CurvedAnimation(
      parent: _trynketLogoController,
      curve: Curves.easeIn,
      reverseCurve: Curves.easeIn,
    );
    Animation<double> animation =
        Tween<double>(begin: 0.02, end: -0.02).animate(_curvedAnimation);

    Timer(Duration(milliseconds: 800), () {
      animation
        ..addStatusListener((status) {
          if (status == AnimationStatus.completed) {
            splashAnimation.state = splashAnimation.state
                .copyWith(repeatShake: splashAnimation.state.repeatShake + 1);
            _trynketLogoController.reverse();
          } else if (status == AnimationStatus.dismissed) {
            splashAnimation.state = splashAnimation.state
                .copyWith(repeatShake: splashAnimation.state.repeatShake + 1);
            _trynketLogoController.forward();
          } else if (splashAnimation.state.repeatShake > 3) {
            animation = Tween<double>(begin: 0.00, end: -0.02)
                .animate(_curvedAnimation);
            _trynketLogoController.stop();
          }
        });
      _trynketLogoController.forward();
    });

    Timer(
        Duration(milliseconds: 3100),
        () => splashAnimation.state =
            splashAnimation.state.copyWith(flipColor: true));

    //logo widgets animation
    Timer(
        Duration(milliseconds: 2290),
        () => splashAnimation.state =
            splashAnimation.state.copyWith(logoWidgets: true));

    final _logoWidgetsOpacityAnimation = Tween(begin: 0.0, end: 1.0).animate(
      CurvedAnimation(parent: _logoWidgetsController, curve: Curves.easeIn),
    );

    AsyncValue<TrynketsUser> asyncUser = useProvider(autoLoginUser);
    Timer(Duration(seconds: 6), () {
      asyncUser.whenData((value) {
        if (value != null) {
          useProvider(trynketUserProvider).state = value;
          Navigator.of(context).pushReplacementNamed(HomeScreen.routeName);
        } else {
          Navigator.of(context).pushReplacementNamed(SelectLanguage.routeName);
        }
      });
    });

    return Scaffold(
      resizeToAvoidBottomPadding: false,
      body: AnimatedContainer(
        color: splashAnimation.state.flipColor
            ? Theme.of(context).primaryColor
            : Colors.white,
        duration: Duration(milliseconds: 400),
        curve: Curves.easeIn,
        width: double.infinity,
        child: !splashAnimation.state.logoWidgets
            ? AnimatedContainer(
                duration: Duration(milliseconds: 250),
                alignment: splashAnimation.state.alignment,
                child: RotationTransition(
                  turns: animation,
                  child: AnimatedContainer(
                    curve: Curves.ease,
                    width: splashAnimation.state.width,
                    duration: Duration(milliseconds: 600),
                    child: Hero(
                      tag: 'logo',
                      child: TrynketLogo(
                          MediaQuery.of(context).size.aspectRatio * 0.2),
                    ),
                  ),
                ),
              )
            : LogoScreenWidgets(
                _logoWidgetsOpacityAnimation, _logoWidgetsController),
      ),
    );
  }
}

Thank you for your time!

rrousselGit commented 3 years ago

Make sure to wrap the Timers inside a useEffect(() {}, [])