Reputation: 11
I am trying to make a login page in Flutter, and want to animate before push to the next page. Everything is working fine except when doing(Bad infos in Login so it is calling "failedLoginAnimation" function and after when I entering right infos in my form) it starts to animate at the end of it my App crash and show me but when i enter right infos directly withou doing a mistake before everything works. PS: I'm new to flutter about a month and even more for animations.
══╡ EXCEPTION CAUGHT BY ANIMATION LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter (11398): The following assertion was thrown while notifying listeners for AnimationController:
I/flutter (11398): Looking up a deactivated widget's ancestor is unsafe.
I/flutter (11398): At this point the state of the widget's element tree is no longer stable. To safely refer to a
I/flutter (11398): widget's ancestor in its dispose() method, save a reference to the ancestor by calling
I/flutter (11398): inheritFromWidgetOfExactType() in the widget's didChangeDependencies() method.
I/flutter (11398):
I/flutter (11398): When the exception was thrown, this was the stack:
I/flutter (11398): #0 Element._debugCheckStateIsActiveForAncestorLookup.<anonymous closure> (package:flutter/src/widgets/framework.dart:3232:9)
I/flutter (11398): #1 Element._debugCheckStateIsActiveForAncestorLookup (package:flutter/src/widgets/framework.dart:3241:6)
I/flutter (11398): #2 Element.ancestorStateOfType (package:flutter/src/widgets/framework.dart:3289:12)
I/flutter (11398): #3 Navigator.of (package:flutter/src/widgets/navigator.dart:1271:19)
I/flutter (11398): #4 Navigator.popAndPushNamed (package:flutter/src/widgets/navigator.dart:825:22)
I/flutter (11398): #5 StaggerAnimationSignIn.build.<anonymous closure> (package:test/premium/pages/login/loginAnimation.dart:146:25)
I/flutter (11398): #6 _AnimationController&Animation&AnimationEagerListenerMixin&AnimationLocalListenersMixin.notifyListeners (package:flutter/src/animation/listener_helpers.dart:124:19)
I/flutter (11398): #7 AnimationController._tick (package:flutter/src/animation/animation_controller.dart:693:5)
I/flutter (11398): #8 Ticker._tick (package:flutter/src/scheduler/ticker.dart:228:5)
I/flutter (11398): #9 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:990:15)
I/flutter (11398): #10 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleBeginFrame.<anonymous closure> (package:flutter/src/scheduler/binding.dart:906:11)
I/flutter (11398): #11 __InternalLinkedHashMap&_HashVMBase&MapMixin&_LinkedHashMapMixin.forEach (dart:collection/runtime/libcompact_hash.dart:370:8)
I/flutter (11398): #12 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleBeginFrame (package:flutter/src/scheduler/binding.dart:904:17)
I/flutter (11398): #13 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleBeginFrame (package:flutter/src/scheduler/binding.dart:834:5)
I/flutter (11398): #14 _invoke1 (dart:ui/hooks.dart:159:13)
I/flutter (11398): #15 _beginFrame (dart:ui/hooks.dart:129:3)
I/flutter (11398):
I/flutter (11398): The AnimationController notifying listeners was:
I/flutter (11398): AnimationController#97d39(⏭ 1.000; paused)
I/flutter (11398): ════════════════════════════════════════════════════════════════════════════════════════════════════
My LoginPage
import 'package:flutter/material.dart';
import 'loginAnimation.dart';
import 'freeAccessAnimation.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/animation.dart';
import 'package:test/premium/tools/jsonTools.dart';
import 'dart:async';
import 'package:test/premium/tools/design/color/designColors.dart';
import './Components/PremiumLink.dart';
import './Components/ForgotPasswordLink.dart';
import './Components/Logo.dart';
import './Components/SignInButton.dart';
import './Components/FreeAccessButton.dart';
import 'package:flutter/services.dart';
import 'package:flutter/scheduler.dart' show timeDilation;
import 'package:test/premium/tools/internet/checkInternet.dart';
import 'package:test/premium/pages/tools/alerts.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:test/premium/variables/variablesApp.dart';
import 'package:test/premium/tools/internet/sendHTTPRequest.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({Key key}) : super(key: key);
@override
LoginScreenState createState() => new LoginScreenState();
}
class LoginScreenState extends State<LoginScreen>
with TickerProviderStateMixin {
static bool hasFinishedLogin;
static bool hasFailedLogin;
static String userLoginToken ;
static String tokenFileExist ;
static String backToLogin;
static bool isFirstCallAction;
static bool hasFinishLogAnim;
AnimationController _loginButtonController;
AnimationController _freeButtonController;
static final usernameController = TextEditingController();
static final passwordController = TextEditingController();
static var animationStatusLogin = 0;
static var animationStatusFree = 0;
Future pause(Duration d) => new Future.delayed(d);
_launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
void tokensManagement(){
if(userLoginToken == null && tokenFileExist == null && backToLogin == "true") {
JTools.deleteFileToken().then((e){
if(e == "Token has been deleted"){
setState(() {
tokenFileExist = 'false';
userLoginToken = null;
backToLogin = 'false';
});
}
});
}else{
JTools.readDataToken().then((e){
String _token = e;
pause(new Duration(milliseconds: 500)).then((e){
JTools.tokenFileExist().then((value){
tokenFileExist = value.toString();
userLoginToken = _token;
}).then((b){
if(_token != null){
if(userLoginToken.isNotEmpty && tokenFileExist == 'true'){
//TODO: on loading
APIToken.token = _token;
CheckInternet().checkInternet().then((e){
if(e == true){
PostHTTP().sendRequestAPI(APIToken.token, 'membership').then((value){
if(value['status'] == "200"){
String membership = value['membership'];
print(membership);
if(membership == "138" || membership == "136"){
print('User is premium');
Navigator.of(context).pushNamed('/main');
}else {
print("User isn't premium ");
JTools.deleteFileToken().then((e) {
if (e == "Token has been deleted") {
setState(() {
tokenFileExist = 'false';
userLoginToken = null;
});
pause(Duration(milliseconds: 1000)).then((e){
Alerts(context: context).alertNotPremium();
});
}
});
}
}else{
if(value['status'] == "404"){
setState(() {
tokenFileExist = 'false';
userLoginToken = null;
backToLogin = 'false';
});
pause(Duration(milliseconds: 1000)).then((e){
Alerts(context: context).alertErrorConnectionServer();
});
}
if(value['status'] == "401" || value['status'] == "403")
{print("User isn't premium ");
JTools.deleteFileToken().then((e) {
if (e == "Token has been deleted") {
setState(() {
tokenFileExist = 'false';
userLoginToken = null;
backToLogin = 'false';
});
pause(Duration(milliseconds: 1000)).then((e){
Alerts(context: context).alertNotPremium();
});
}
});}
}
});
}else{
Alerts(context: context).alertInternet();
failedLoginAnimation();
}
});
}
else{
setState(() {
});
}
}else{
setState(() {
});
}
});
});
});
}
}
@override
void initState() {
super.initState();
_loginButtonController = new AnimationController(
duration: new Duration(milliseconds: 3000), vsync: this);
_freeButtonController = new AnimationController(
duration: new Duration(milliseconds: 3000), vsync: this);
tokensManagement();
animationStatusLogin = 0;
animationStatusFree = 0;
print('init LOGIN');
}
@override
void dispose() {
super.dispose();
_loginButtonController.dispose();
_freeButtonController.dispose();
}
Future<Null> _playAnimationLogin() async {
try {
await _loginButtonController.forward();
} on TickerCanceled {}
}
Future<Null> _playAnimationFree() async {
try {
await _freeButtonController.forward();
await _freeButtonController.reverse();
} on TickerCanceled {}
}
Widget formLogin(){
return new Container(
margin: new EdgeInsets.symmetric(horizontal: 20.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Form(
child: new Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
new Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(
width: 0.5,
color: DesignColors.animatedLoginColor,
),
),
),
child: new TextFormField(
controller: usernameController,
keyboardType: TextInputType.emailAddress,
style: const TextStyle(
color: Colors.white,
),
decoration: new InputDecoration(
icon: new Icon(
Icons.person_outline,
color: Colors.white,
),
border: InputBorder.none,
hintText: "Email/Username",
hintStyle: const TextStyle(color: Colors.white, fontSize: 15.0),
contentPadding: const EdgeInsets.only(
top: 30.0, right: 30.0, bottom: 30.0, left: 5.0),
),
),
),
new Container(
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(
width: 0.5,
color: DesignColors.animatedLoginColor,
),
),
),
child: new TextFormField(
controller: passwordController,
obscureText: true,
style: const TextStyle(
color: Colors.white,
),
decoration: new InputDecoration(
icon: new Icon(
Icons.lock_outline,
color: Colors.white,
),
border: InputBorder.none,
hintText: "Password",
hintStyle: const TextStyle(color: Colors.white, fontSize: 15.0),
contentPadding: const EdgeInsets.only(
top: 30.0, right: 30.0, bottom: 30.0, left: 5.0),
),
),
),
],
)),
],
));
}
void failedLoginAnimation(){
setState(() {
hasFailedLogin = true;
_playAnimationLogin();
animationStatusLogin = 0;
});
}
Widget logPage(){
return new WillPopScope(
onWillPop: () async => false,
child: new Scaffold(
backgroundColor: DesignColors.backgroundColor,
body: Center(
child: Theme(
data: ThemeData(splashColor: Colors.transparent, textSelectionHandleColor: Colors.blue, highlightColor: Colors.transparent),
child:new ListView(
shrinkWrap: true,
children: <Widget>[
new Stack(
alignment: AlignmentDirectional.bottomCenter,
children: <Widget>[
new Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new Logo(image: DecorationImage(image: ExactAssetImage('assets/images/AppLogo.png'), fit: BoxFit.cover),),
formLogin(),
new PremiumLink(action: (){_launchURL("someurl");}),
new ForgotPasswordLink(action: (){_launchURL("someurl");}),
],
),
animationStatusLogin == 0
? new Padding(
padding: const EdgeInsets.only(bottom: 160.0),
child: new InkWell(
onTap: () {
setState(() {
animationStatusLogin = 1;
hasFinishedLogin = false;
hasFinishLogAnim = false;
hasFailedLogin = false;
isFirstCallAction = true;
});
_playAnimationLogin();
},
child: new SignIn()),
)
: new StaggerAnimationSignIn( action: (){
CheckInternet().checkInternet().then((e){
if(e == true){
PostHTTP().sendLogin({'username': usernameController.text, 'password': passwordController.text}).then((e){
String responseStatus = e['status'];
if(responseStatus != "200"){
Alerts(context: context).alertFalseInfosLogin();
failedLoginAnimation();
}else{
String token = e['token'];
PostHTTP().sendRequestAPI(token, 'membership').then((value){
if(value['status'] == "200"){
String membership = value['membership'];
print(membership);
if(membership == "138" || membership == "136"){
APIToken.token = token;
JTools.saveLogin(token);
print('User is premium ');
FocusScope.of(context).requestFocus(new FocusNode());
hasFinishedLogin = true;
hasFinishLogAnim = false;
hasFailedLogin = false;
_playAnimationLogin();
}else{
print("User isn't premium ");
Alerts(context: context).alertNotPremium();
failedLoginAnimation();
}
}else{
if(value['status'] == "404"){Alerts(context: context).alertErrorConnectionServer(); failedLoginAnimation();}
if(value['status'] == "401" || value['status'] == "403"){Alerts(context: context).alertNotPremium(); failedLoginAnimation();}
}
});
}
});
}else{
Alerts(context: context).alertInternet();
failedLoginAnimation();
}
});
//Navigator.of(context).popAndPushNamed('/main');
},
buttonController:
_loginButtonController.view
),
animationStatusFree == 0
? new Padding(
padding: const EdgeInsets.only(bottom: 85.0),
child: new InkWell(
onTap: () {
setState(() {
animationStatusFree = 1;
});
_playAnimationFree();
},
child: new FreeAccess()),
)
: new StaggerAnimationFree(
buttonController:
_freeButtonController.view),
],
),
],
),
),
)
)
);
}
Widget waitingPage(){
return WillPopScope(child: Scaffold(backgroundColor: DesignColors.backgroundColor.withOpacity(1), body: Center(child: new CircularProgressIndicator(
value: null,
strokeWidth: 1.0,
valueColor: new AlwaysStoppedAnimation<Color>(
DesignColors.robot1Theme),
)),), onWillPop:() async => false);
}
@override
Widget build(BuildContext context) {
timeDilation = 0.4;
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);
if(userLoginToken == null && tokenFileExist == 'false'){
return logPage();
}
if(userLoginToken == null && tokenFileExist == null && backToLogin == "true"){
//TODO: on loading
backToLogin ="false";
tokensManagement();
return logPage();
}else if(userLoginToken == null && tokenFileExist == null){
//TODO: on loading
return waitingPage();
}
if(userLoginToken.isNotEmpty && tokenFileExist == null){
//TODO: on loading
return waitingPage();
}
if(userLoginToken.isEmpty && tokenFileExist == 'true'){
return logPage();
}
return logPage();
}
}
My Animation class
import 'package:flutter/material.dart';
import 'package:test/premium/tools/design/color/designColors.dart';
import 'login.dart';
class StaggerAnimationSignIn extends StatelessWidget {
final action;
StaggerAnimationSignIn({Key key, this.buttonController, @required this.action})
: buttonSqueezeanimation = new Tween(
begin: 320.0,
end: 70.0,
)
.animate(
new CurvedAnimation(
parent: buttonController,
curve: new Interval(
0.0,
0.175,
),
),
),
buttomZoomOut = new Tween(
begin: 70.0,
end: 5000.0,
)
.animate(
new CurvedAnimation(
parent: buttonController,
curve: new Interval(
0.55,
0.999,
curve: Curves.bounceOut,
),
),
),
containerCircleAnimation = new EdgeInsetsTween(
begin: const EdgeInsets.only(bottom: 160.0),
end: const EdgeInsets.only(bottom: 0.0),
)
.animate(
new CurvedAnimation(
parent: buttonController,
curve: new Interval(
0.500,
0.800,
curve: Curves.ease,
),
),
),
super(key: key);
final AnimationController buttonController;
final Animation<EdgeInsets> containerCircleAnimation;
final Animation buttonSqueezeanimation;
final Animation buttomZoomOut;
Widget _buildAnimation(BuildContext context, Widget child) {
return new Padding(
padding: buttomZoomOut.value == 80
? const EdgeInsets.only(bottom: 160.0)
: containerCircleAnimation.value,
child: new InkWell(
onTap: () {
},
child: new Hero(
tag: "fadeLogin",
child: buttomZoomOut.value <= 300
? new Container(
width: buttomZoomOut.value == 70
? buttonSqueezeanimation.value
: buttomZoomOut.value,
height:
buttomZoomOut.value == 70 ? 60.0 : buttomZoomOut.value,
alignment: FractionalOffset.center,
decoration: new BoxDecoration(
color: DesignColors.animatedLoginColor,
borderRadius: buttomZoomOut.value < 400
? new BorderRadius.all(const Radius.circular(30.0))
: new BorderRadius.all(const Radius.circular(0.0)),
),
child: buttonSqueezeanimation.value > 75.0
? new Text(
"Sign In",
style: new TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.w300,
letterSpacing: 0.3,
),
)
: buttomZoomOut.value < 300.0
? new CircularProgressIndicator(
value: null,
strokeWidth: 1.0,
valueColor: new AlwaysStoppedAnimation<Color>(
Colors.white),
)
: null)
: new Container(
width: buttomZoomOut.value,
height: buttomZoomOut.value,
decoration: new BoxDecoration(
shape: buttomZoomOut.value < 500
? BoxShape.circle
: BoxShape.rectangle,
color: DesignColors.animatedLoginColor,
),
),
)),
);
}
@override
Widget build(BuildContext context) {
if(LoginScreenState.isFirstCallAction){
buttonController.addListener(() {
if (LoginScreenState.isFirstCallAction) {
print("1");
LoginScreenState.isFirstCallAction = false;
action();
}
if (LoginScreenState.hasFailedLogin) {
print("3");
LoginScreenState.hasFailedLogin = false;
buttonController.reverse();
LoginScreenState.animationStatusLogin = 0;
}
if (buttonController.value > 0.2 &&
LoginScreenState.hasFinishedLogin == false &&
LoginScreenState.hasFailedLogin == false &&
LoginScreenState.hasFinishLogAnim == false) {
print("2");
buttonController.stop();
}
if (LoginScreenState.hasFinishedLogin && !LoginScreenState.hasFailedLogin && !LoginScreenState.hasFinishLogAnim && buttonController.value == 1.0) {
print("4");
LoginScreenState.hasFinishedLogin = false;
LoginScreenState.hasFailedLogin = false;
LoginScreenState.hasFinishLogAnim = true;
LoginScreenState.animationStatusLogin = 0;
Navigator.popAndPushNamed(context, "/main");
return;
}
});
}
return new AnimatedBuilder(
builder: _buildAnimation,
animation: buttonController,
);
}
}
Update:
HI fixed the problems I think, I still need to to do few test but seems that calling Navigator.popAndPushNamed(context, "/main");
in the AnimationClass wasn't a good idea I just changed my Login class in a way that when animation is finished I call Navigator.popAndPushNamed(context, "/main");
in my login class. i'll keep updating this post in the incoming days.
New start in my build login class
@override
Widget build(BuildContext context) {
_loginButtonController.addListener((){
if(_loginButtonController.isCompleted){
FocusScope.of(context).requestFocus(new FocusNode());
Navigator.popAndPushNamed(context, "/main");
}
});
And removed Navigator.popAndPushNamed(context, "/main");
and return;
in My Animation end call
Upvotes: 1
Views: 8565
Reputation: 10473
While I'm unable to run the minimal repro provided, going through the logs, it seems that you're trying to access a context from the content that has been popped or disposed of.
If you're able to replicate the behavior you've reported on a minimal repro, it'll be easier to identify the cause. Here's a complete sample code that I'm playing with.
import 'package:flutter/material.dart';
class StaggerAnimation extends StatelessWidget {
StaggerAnimation({Key key, this.controller})
:
// Each animation defined here transforms its value during the subset
// of the controller's duration defined by the animation's interval.
// For example the opacity animation transforms its value during
// the first 10% of the controller's duration.
opacity = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.0,
0.100,
curve: Curves.ease,
),
),
),
width = Tween<double>(
begin: 50.0,
end: 150.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.125,
0.250,
curve: Curves.ease,
),
),
),
height = Tween<double>(begin: 50.0, end: 150.0).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.250,
0.375,
curve: Curves.ease,
),
),
),
padding = EdgeInsetsTween(
begin: const EdgeInsets.only(bottom: 16.0),
end: const EdgeInsets.only(bottom: 75.0),
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.250,
0.375,
curve: Curves.ease,
),
),
),
borderRadius = BorderRadiusTween(
begin: BorderRadius.circular(4.0),
end: BorderRadius.circular(75.0),
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.375,
0.500,
curve: Curves.ease,
),
),
),
color = ColorTween(
begin: Colors.indigo[100],
end: Colors.orange[400],
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.500,
0.750,
curve: Curves.ease,
),
),
),
super(key: key);
final Animation<double> controller;
final Animation<double> opacity;
final Animation<double> width;
final Animation<double> height;
final Animation<EdgeInsets> padding;
final Animation<BorderRadius> borderRadius;
final Animation<Color> color;
// This function is called each time the controller "ticks" a new frame.
// When it runs, all of the animation's values will have been
// updated to reflect the controller's current value.
Widget _buildAnimation(BuildContext context, Widget child) {
return Container(
padding: padding.value,
alignment: Alignment.bottomCenter,
child: Opacity(
opacity: opacity.value,
child: Container(
width: width.value,
height: height.value,
decoration: BoxDecoration(
color: color.value,
border: Border.all(
color: Colors.indigo[300],
width: 3.0,
),
borderRadius: borderRadius.value,
),
),
),
);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
builder: _buildAnimation,
animation: controller,
);
}
}
class StaggerDemo extends StatefulWidget {
@override
_StaggerDemoState createState() => _StaggerDemoState();
}
class _StaggerDemoState extends State<StaggerDemo>
with TickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Future<void> _playAnimation() async {
try {
await _controller.forward().orCancel;
await _controller.reverse().orCancel;
} on TickerCanceled {
// the animation got canceled, probably because we were disposed
}
}
@override
Widget build(BuildContext context) {
var timeDilation = 10.0; // 1.0 is normal animation speed.
return Scaffold(
appBar: AppBar(
title: const Text('Staggered Animation'),
),
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
_playAnimation();
},
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(
width: 300.0,
height: 300.0,
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.1),
border: Border.all(
color: Colors.black.withOpacity(0.5),
),
),
child: StaggerAnimation(controller: _controller.view),
),
ElevatedButton(
child: Text('Open route'),
onPressed: () {
// Navigate to second route when tapped.
Navigator.popAndPushNamed(context, '/second');
},
),
],
),
),
),
);
}
}
void main() {
runApp(StaggerDemoApp());
}
class StaggerDemoApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// Start the app with the "/" named route. In this case, the app starts
// on the FirstScreen widget.
initialRoute: '/',
routes: {
// When navigating to the "/" route, build the FirstScreen widget.
'/': (context) => StaggerDemo(),
// When navigating to the "/second" route, build the SecondScreen widget.
'/second': (context) => MyHomePage(title: 'Second Page',),
},
theme: ThemeData(
primarySwatch: Colors.blue,
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
),
);
}
}
From what I've observed, popping the screen mid-animation and pushing a new screen doesn't seem to cause any errors. As what the logs have mentioned, the triggered listeners set on the AnimationControllers are the ones causing the issue.
Upvotes: 1