How to call ConfettiController from another class - flutter

Inside MyAppState I have created a ConfettiController called controllerTopCenter that I got from this package - https://pub.dev/packages/confetti
I also added a key to MyApp in the hopes of triggering the confetti animation from another class.
In the other class I added GlobalKey<MyAppState> key = GlobalKey<MyAppState>(); and then added key.currentState.controllerTopCenter.play(); which will trigger every time a certain thing is achieved within the app. However when it is triggered I get the error message "NoSuchMethodError: invalid member on null: 'controllerTopCenter'".
I'm wondering what is causing this error.
Here is my code:
import 'package:flutter/material.dart';
import 'package:confetti/confetti.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
ConfettiController controllerTopCenter;
void initState() {
controllerTopCenter =
ConfettiController(duration: const Duration(seconds: 10))
..addListener(() => setState(() {}));
super.initState();
}
void dispose() {
controllerTopCenter.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
Tap tap = new Tap();
return MaterialApp(
title: 'Sum Mini',
home: Scaffold(
body: Column(
children: [
Align(
alignment: Alignment.center,
child: ConfettiWidget(
confettiController: controllerTopCenter,
blastDirectionality: BlastDirectionality
.explosive, // don't specify a direction, blast randomly
shouldLoop:
true, // start again as soon as the animation is finished
colors: const [
Colors.green,
Colors.blue,
Colors.pink,
Colors.orange,
Colors.purple
], // manually specify the colors to be used
),
),
GestureDetector(
onTap: () {
tap.onTap();
},
child: Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
color: Colors.greenAccent,
shape: BoxShape.circle,
),
),
),
],
),
));
}
}
class Tap {
GlobalKey<MyAppState> key = GlobalKey<MyAppState>();
onTap() {
key.currentState.controllerTopCenter.play();
}
}
Any help would be greatly appreciated.

You can copy paste run full code below
You can move Tap tap outside from build and pass to MyApp
code snippet
Tap tap = new Tap();
void main() => runApp(MyApp(key: tap.key));
working demo
full code
import 'package:flutter/material.dart';
import 'package:confetti/confetti.dart';
Tap tap = new Tap();
void main() => runApp(MyApp(key: tap.key));
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
ConfettiController controllerTopCenter;
void initState() {
controllerTopCenter =
ConfettiController(duration: const Duration(seconds: 10))
..addListener(() => setState(() {}));
super.initState();
}
void dispose() {
controllerTopCenter.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Sum Mini',
home: Scaffold(
body: Column(
children: [
Align(
alignment: Alignment.center,
child: ConfettiWidget(
confettiController: controllerTopCenter,
blastDirectionality: BlastDirectionality
.explosive, // don't specify a direction, blast randomly
shouldLoop:
true, // start again as soon as the animation is finished
colors: const [
Colors.green,
Colors.blue,
Colors.pink,
Colors.orange,
Colors.purple
], // manually specify the colors to be used
),
),
GestureDetector(
onTap: () {
tap.onTap();
},
child: Container(
width: 100.0,
height: 100.0,
decoration: BoxDecoration(
color: Colors.greenAccent,
shape: BoxShape.circle,
),
),
),
],
),
));
}
}
class Tap {
GlobalKey<MyAppState> key = GlobalKey<MyAppState>();
onTap() {
key.currentState.controllerTopCenter.play();
}
}

Related

CustomPainter's paint method is not getting called before WidgetsBinding.instance.addPostFrameCallback in case of Multiple navigation

I have a Flutter StatefulWidget and in initState() method I am using WidgetsBinding.instance.addPostFrameCallback to use one instance variable (late List _tracks). like -
WidgetsBinding.instance.addPostFrameCallback((_) {
for(itr = 0; itr<_tracks.length; itr++){
// some logic
}
});
As this would get invoked after all Widgets are done. In one of the CustomPaint's painter class I am initializing that variable.
SizedBox.expand(
child: CustomPaint(
painter: TrackPainter(
trackCalculationListener: (tracks) {
_tracks = tracks;
}),
),
),
It is working fine when I have one screen, i.e the same class. But, When I am adding one screen before that and trying to navigate to this screen from the new screen it is throwing _tracks is not initialized exception.
new screen is very basic -
class MainMenu extends StatefulWidget {
const MainMenu({super.key});
#override
State<MainMenu> createState() => _MainMenuState();
}
class _MainMenuState extends State<MainMenu> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.white,
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const Play(),
maintainState: false));
},
child: const Text('play game'),
),
),
);
}
}
In single screen case the paint method of painter is getting called before postFrameCallback but in case of multiple it is not getting before postFrameCallback and because of that the variable is not getting initialized.
reproducible code -
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 MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: {
'/mainMenu': (context) => const MainMenu(),
'/game': (context) => const MyHomePage(title: 'game'),
},
initialRoute: '/mainMenu',
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late List<Rect> _playerTracks;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
print(_playerTracks.length);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
color: Colors.white,
margin: const EdgeInsets.all(20),
child: AspectRatio(
aspectRatio: 1,
child: SizedBox.expand(
child: CustomPaint(
painter: RectanglePainter(
trackCalculationListener: (playerTracks) =>
_playerTracks = playerTracks),
),
),
),
)
],
),
),
);
}
}
class MainMenu extends StatefulWidget {
static String route = '/mainMenu';
const MainMenu({super.key});
#override
State<MainMenu> createState() => _MainMenuState();
}
class _MainMenuState extends State<MainMenu> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
height: 200.0,
color: Colors.white,
child: ElevatedButton(
onPressed: () {
Navigator.pushNamed(context, '/game');
},
child: const Text('play game'),
),
),
),
);
}
}
class RectanglePainter extends CustomPainter {
Function(List<Rect>) trackCalculationListener;
RectanglePainter({required this.trackCalculationListener});
#override
void paint(Canvas canvas, Size size) {
final Rect rect = Offset.zero & size;
const RadialGradient gradient = RadialGradient(
center: Alignment(0.7, -0.6),
radius: 0.2,
colors: <Color>[Color(0xFFFFFF00), Color(0xFF0099FF)],
stops: <double>[0.4, 1.0],
);
canvas.drawRect(
rect,
Paint()..shader = gradient.createShader(rect),
);
List<Rect> _playerTracks = [];
_playerTracks.add(rect);
trackCalculationListener(_playerTracks);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
I am very new to flutter and would highly appreciate if someone could help me figure out what I am doing wrong here.

Flutter Web on Safari: Videos behave inconsistent (some start playing even when paused, some don't)

When trying to play two (or more) videos in Safari, the first video requires user interaction to play, so I added a play button. Then, when the next video widget appears, the video will start automatically even if the video controller was paused. Now the button shows the play icon but the video is already playing... How can I fix this?
Also it looks like the controller of the second video didn't add the listener since no print statement is executed (until the button is pressed).
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
void main() => runApp(const VideoDemo());
class VideoDemo extends StatelessWidget {
const VideoDemo({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.first_page)),
Tab(icon: Icon(Icons.last_page)),
],
),
title: const Text('Video Demo'),
),
body: const TabBarView(
children: [
VideoWidget(),
VideoWidget(),
],
),
),
),
);
}
}
class VideoWidget extends StatefulWidget {
const VideoWidget({Key? key}) : super(key: key);
#override
_VideoWidgetState createState() => _VideoWidgetState();
}
class _VideoWidgetState extends State<VideoWidget> {
late VideoPlayerController _controller;
bool hasLoaded = false;
#override
void initState() {
super.initState();
_controller = VideoPlayerController.network(
'https://flutter.github.io/assets-for-api-docs/assets/videos/bee.mp4')
..setLooping(true)
..addListener(videoListener)
..initialize().then((_) {
setState(() {
hasLoaded = true;
});
})
..pause(); // <-- PAUSE the video
}
#override
Widget build(BuildContext context) => Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.red,
child: Stack(
alignment: Alignment.center,
children: [
_controller.value.isInitialized
? VideoPlayer(_controller)
: const Center(child: CircularProgressIndicator()),
SizedBox(
height: 50,
width: 130,
child: ElevatedButton(
onPressed: () {
setState(() {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
});
},
child: Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
)),
],
));
void videoListener() { // <-- Listener does not fire on second video until play button is pressed
if (hasLoaded) {
print("isPlaying: ${_controller.value.isPlaying}");
}
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
}

AnimatedPositioned - how to change position automatically without pressing button

i want to use the animated positioned class to change widget position with the launch of the screen without pressing a button!
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool selected = false;
#override
Widget build(BuildContext context) {
return SizedBox(
width: 200,
height: 350,
child: Stack(
children: <Widget>[
AnimatedPositioned(
width: selected ? 200.0 : 50.0,
height: selected ? 50.0 : 200.0,
top: selected ? 50.0 : 150.0,
duration: const Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
child: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Container(
color: Colors.blue,
child: const Center(child: Text('Tap me')),
),
),
),
],
),
);
}
}
i tried to change it by using a WidgetsBinding like this :
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
selected = true;
});
print(' widget binding : $selected');
});
it works fine but the problem was that it doesnt excuted once, it keeps running and change the value to true! like this :
I copy/past the code you have shared to see what's going on, and it's working just fine for me. I have this code
import 'package:flutter/material.dart';
void main() async {
return runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
selected = true;
});
print(' widget binding : $selected');
});
}
bool selected = false;
#override
Widget build(BuildContext context) {
return SizedBox(
width: 200,
height: 350,
child: Stack(
children: <Widget>[
AnimatedPositioned(
width: selected ? 200.0 : 50.0,
height: selected ? 50.0 : 200.0,
top: selected ? 50.0 : 150.0,
duration: const Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
child: GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Container(
color: Colors.blue,
child: const Center(child: Text('Tap me')),
),
),
),
],
),
);
}
}
The animation is run only once, when the postFrameCallback is called. The problem must be elsewhere

flutter navigate new screen after animatedcontainer onEnd:

I'am noob of flutter and i want a create some apps. This is my main screen with animated container.
import 'package:flutter/material.dart';
import "./loginmenu.dart";
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
backgroundColor: Color.fromRGBO(197, 197, 187, 1),
body: AnimatedContainer(
duration: Duration(milliseconds: 350),
width: double.infinity,
height: double.infinity,
child: Image(image: AssetImage("assets/images/Babapps-logos.jpeg")),
onEnd: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => loginscreen()),
),
),
),
);
}
}
when animation duration finish i want go new screen.
import 'package:flutter/material.dart';
class loginscreen extends StatefulWidget {
const loginscreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
children: <Widget>[
Container(width: double.infinity, margin: EdgeInsets.all(130)),
Container(
width: double.infinity,
margin: EdgeInsets.all(5),
child: Center(
child:
Text("Welcome Diet&Life", style: TextStyle(fontSize: 19)),
),
),
Container(
width: 320,
margin: EdgeInsets.all(5),
child: Center(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(), hintText: "Username"),
),
),
),
Container(
width: 320,
margin: EdgeInsets.all(5),
child: Center(
child: TextField(
decoration: InputDecoration(
border: OutlineInputBorder(), hintText: "Password"),
)),
),
Container(
child: Center(
child: RaisedButton(
child: Text("Login"),
onPressed: null,
),
),
),
Container(
margin: EdgeInsets.all(10),
child: Center(
child: new InkWell(
child: Text("Don't have an account?"), onTap: null),
),
)
],
),
),
);
}
#override
State<StatefulWidget> createState() {
// TODO: implement createState
throw UnimplementedError();
}
}
but when I run this code, the animation does not go to the other screen even though it expires. Am I on the right track or do I have to work with setstate?
There is nothing changing on Container, therefor animation onEnd never gets call.
You need to change something inside the container in order to animate it.
If you just like to navigate after some delay, Just use Future.delayed then navigate.
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return const MaterialApp(home: StartPage());
}
}
class StartPage extends StatefulWidget {
const StartPage({Key? key}) : super(key: key);
#override
State<StartPage> createState() => _StartPageState();
}
class _StartPageState extends State<StartPage> {
Color containerColor = Colors.cyanAccent;
#override
void initState() {
super.initState();
_nextPage();
}
/// just nevigate
_nextPage() async {
Future.delayed(Duration(seconds: 1)).then((value) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AnimE(),
),
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
containerColor = Colors.blue;
});
},
),
body: AnimatedContainer(
duration: const Duration(seconds: 2),
color: containerColor,
width: double.infinity,
height: double.infinity,
child: const Text("a"),
onEnd: () {
print("Ebnd"); // paste neviagte by removing init
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => loginscreen(),
// ),
// );
}),
);
}
}

How to update Draggable feedback widget while dragging?

I'm trying to make a game where I need to be able to click on a button while dragging a widget and that button should render som changes to the feedback widget that the user is currently dragging. The childWhenDragging updates just fine but the feedback widget doesn't update during the drag. Is there any way to achieve this?
This is a basic example to recreate it.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Draggable Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int counter;
#override
void initState() {
this.counter = 0;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Draggable Test'),
),
body: Column(
children: <Widget>[
Draggable(
child: Container(
color: Colors.red,
width: 100,
height: 100,
child: Text(counter.toString()),
),
feedback: Container(
color: Colors.red,
width: 100,
height: 100,
child: Text(counter.toString()),
),
childWhenDragging: Container(
color: Colors.red,
width: 100,
height: 100,
child: Text(counter.toString()),
),
),
RaisedButton(
onPressed: () {
setState(() {
counter += 1;
});
},
child: Text("plus"),
)
],
),
);
}
}
I expect the feedback widget to render the correct counter value but it never updates.