Flutter does not update subwidget - ios

I have a widget, which contains a subwidget. It get the last and new value in the build method like this:
children: <Widget>[
AspectRatio(
aspectRatio: 1.0,
child: Container(
padding: const EdgeInsets.all(16.0),
child: CircleWidget(_lastWindSpeed/10, _speed/10),
))
],
),
The state will be updatet with
setState
But the widget does not get updated if there are new values.
Did anyone see the issue there?
The class is:
import 'package:flutter/material.dart';
import 'circle_painter.dart';
class CircleWidget extends StatefulWidget {
final double _start;
final double _finish;
CircleWidget(this._start, this._finish);
#override
State<CircleWidget> createState() => new _CircleState();
}
class _CircleState extends State<CircleWidget> with SingleTickerProviderStateMixin{
Animation<double> animation;
double _fraction;
#override
void initState() {
super.initState();
var controller = AnimationController(duration: new Duration(milliseconds: 10), vsync: this);
animation = Tween(begin: widget._start, end: widget._finish).animate(controller)
..addListener((){
setState(() {
_fraction = animation.value;
});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
return new CustomPaint(
painter: new CirclePainter(_fraction));
}
}
Thanks a lot.

If you want your animation to restart when the values of the CircleWidget change, you need to use the didUpdateWidget lifecycle. initState is called only once, while didUpdateWidget is called every time your the corresponding widget is recreated - note that the values might be the same if a parent widget rebuilt too.
#override
void didUpdateWidget(CircleWidget oldWidget) {
if (oldWidget._start != widget._start ||
oldWidget._end != widget._end) {
// values changed, restart animation.
controller
..reset()
..forward();
}
super.didUpdateWidget(oldWidget);
}

I want to post an alternative solution to the given solution above.
if you want StatefulWidget to update its underlying data when you call setState or inside a StreamBuilder you should pass a UniqueKey to the StatefulWidget constructor.
The behavior of fullter when setState is called is to check if the type did not change in case of StatefulWidget if not nothing will be updated.
If you add a UniqueKey to the constuctor, the flutter UI updater will check the key instead.
I hope this helps.
import 'package:flutter/material.dart';
import 'circle_painter.dart';
class CircleWidget extends StatefulWidget {
final double _start;
final double _finish;
CircleWidget(this._start, this._finish, Key:key):super(key:key); // <-- check this
#override
State<CircleWidget> createState() => new _CircleState();
}
class _CircleState extends State<CircleWidget> with SingleTickerProviderStateMixin{
Animation<double> animation;
double _fraction;
#override
void initState() {
super.initState();
var controller = AnimationController(duration: new Duration(milliseconds: 10), vsync: this);
animation = Tween(begin: widget._start, end: widget._finish).animate(controller)
..addListener((){
setState(() {
_fraction = animation.value;
});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
return new CustomPaint(
painter: new CirclePainter(_fraction));
}
}
children: <Widget>[
AspectRatio(
aspectRatio: 1.0,
child: Container(
padding: const EdgeInsets.all(16.0),
child: CircleWidget(_lastWindSpeed/10, _speed/10, key: UniqueKey()), // <---- add UniqueKey as key param here to tell futter to check keys instead of types
))
],
),

Related

How can I pass the callback to another StatefulWidget?

For example, I have two StatefulWidget to monitor the same callback method. How should I do this? In case I have more than three StatefulWidget to monitor its events?
class WidgetOne extends StatefulWidget {
#override
_WidgetOneState createState() => new _WidgetOneState();
}
class _WidgetOneState extends State<WidgetOne> {
// this is the callback, the widget two want listen the callback too
bool _onNotification(ScrollNotification notification){
}
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new NotificationListener(child: new ListView(shrinkWrap: true,),
onNotification: _onNotification),
new WidgetTwo()
],
);
}
}
class WidgetTwo extends StatefulWidget {
#override
_WidgetTwoState createState() => new _WidgetTwoState();
}
class _WidgetTwoState extends State<WidgetTwo> {
// in this,How Can I get the callback in WidgetOne?
#override
Widget build(BuildContext context) {
return new Container();
}
}
You can't and shouldn't. Widgets should never depends on the architecture of other widgets.
You have two possibilities :
Merge WidgetTwo and WidgetOne. As separating them doesn't makes sense (at least with what you provided).
Modify WidgetTwo to take a child. And add that ListView as child of WidgetTwo. So that it can wrap the list into it's own NotificationListener.
Your solution could be possible by using setState() and pass your state function in constructor of WidgetTwo. I did an example below, main idea of this example is I have MyHomePage as my main Widget and MyFloatButton (which I want to customise as another StatefulWidget), so when pressing the FAB i need to call increment counter function in MyHomePage. Lets take a look below how I do that.
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
//Consider this function as your's _onNotification and important to note I am using setState() within :)
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(
widget.title,
style: TextStyle(color: Colors.white),
),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'You have pushed the button $_counter times:',
style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
),
],
),
),
floatingActionButton: new MyFloatButton(_incrementCounter),//here I am calling MyFloatButton Constructor passing _incrementCounter as a function
);
}
}
class MyFloatButton extends StatefulWidget {
final Function onPressedFunction;
// Here I am receiving the function in constructor as params
MyFloatButton(this.onPressedFunction);
#override
_MyFloatButtonState createState() => new _MyFloatButtonState();
}
class _MyFloatButtonState extends State<MyFloatButton> {
#override
Widget build(BuildContext context) {
return new Container(
padding: EdgeInsets.all(5.0),
decoration: new BoxDecoration(color: Colors.orangeAccent, borderRadius: new BorderRadius.circular(50.0)),
child: new IconButton(
icon: new Icon(Icons.add),
color: Colors.white,
onPressed: widget.onPressedFunction,// here i set the onPressed property with widget.onPressedFunction. Remember that you should use "widget." in order to access onPressedFunction here!
),
);
}
}
Now Consider MyHomePage as your WidgetOne, MyFloatButton as your WidgetTwo and _incrementCounter function as your _onNotification. Hope you will achieve what you want :)
(I did example generically so anyone can understand according to scenario they are facing)
You can use the built-in widget property for stateful widgets.
https://api.flutter.dev/flutter/widgets/State/widget.html
So in WidgetOne it will be
new WidgetTwo(callback: callback)
in WidgetTwo
class WidgetTwo extends StatefulWidget {
final Function callback;
WidgetTwo({this.callback});
#override
_WidgetTwoState createState() => new _WidgetTwoState();
}
and in _WidgetTwoState you can access it as
widget.callback

`vsync` property in TabController constructor

According to this: sample code
I created my own implementation of TabController:
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = new TabController(vsync: this, length: choices.length);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
bottomNavigationBar: new Material(
color: Colors.blue,
child: new TabBar(
controller: _tabController,
isScrollable: false,
tabs: choices.map((Choice choice) {
return new Tab(
text: null,
icon: new Icon(choice.icon),
);
}).toList(),
),
),
appBar: new AppBar(
title: const Text('Swap'),
),
body: new TabBarView(
controller: _tabController,
children: choices.map((Choice choice) {
return new Padding(
padding: const EdgeInsets.all(16.0),
child: new ChoiceCard(choice: choice),
);
}).toList(),
),
),
);
}
}
In line: _tabController = new TabController(vsync: this, length: choices.length); I got error this message:
error: The argument type '_MyAppState' can't be assigned to the parameter type 'TickerProvider'. (argument_type_not_assignable at [swap] lib/main.dart:24)
What is wrong with my code?
Add with TickerProviderStateMixin to the end of your State’s class declaration.
Simply add with TickerProviderStateMixin at the end of extends state class as follows:
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
//...
}
As Answered earlier adding the mixin, TickerProviderStateMixin should do the job or you can also use SingleTickerProviderStateMixin if you only need a Single Ticker.
But what is Does TickerProviders really do?
vsync takes a TickerProvider as an argument , that's why we use SingleTickerProviderStateMixin and as the named describes TickerProvider provides Ticker which simply means it tells our app about the Frame update(or Screen Update), so that our AnimationController can generate a new value and we can redraw the animated widget.
Question is very generic, so need to describe more
Vsync used for
vsync is the property that represents the TickerProvider (i.e., Tick
is similar to clock's tick which means that at every certain duration
TickerProvider will render the class state and redraw the object.)
vsync property is required only on that constructor which requires to render its class state at every certain off-set time when we need to render our components or widgets to redraw and reflect the UI.
vsync can be used with the classes which require certain transition or animation to re-render to draw different objects.
Internal Implementation
TabController({ int initialIndex = 0, #required this.length, #required TickerProvider vsync })
: assert(length != null && length >= 0),
assert(initialIndex != null && initialIndex >= 0 && (length == 0 || initialIndex < length)),
_index = initialIndex,
_previousIndex = initialIndex,
_animationController = AnimationController.unbounded(
value: initialIndex.toDouble(),
vsync: vsync,
);
TabController uses AnimationController internally for the rendering of the tab bar state
Add TickerProviderStateMixin at the end of class state
Here is the full example
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> with TickerProviderStateMixin {
MotionTabController? _tabController;
#override
void initState() {
super.initState();
_tabController = new MotionTabController(initialIndex: 1, vsync: this);
}
#override
void dispose() {
super.dispose();
_tabController!.dispose();
}
#override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}
The above answers are correct but you have to declare a tabbar in class and initialize the tabbar from iniState, else the vsync variable doesn't accept 'this'
Following code may help you.
class _MatchesState extends State<Matches> with SingleTickerProviderStateMixin {
TabController? tabController;
#override
void initState() {
tabController = TabController(
length: 2,
vsync: this,
initialIndex: 0,
);
super.initState();
}
In GetX
I found a solution just add with SingleGetTickerProviderMixin to be the full code as the below one:
import 'package:get/get.dart';
import 'package:flutter/material.dart';
class ControllerViewModel extends GetxController with GetSingleTickerProviderStateMixin {
AnimationController _controller;
#override
void onInit() {
// TODO: implement onInit
super.onInit();
_controller = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 2500,
),
);
}
}
Add any of these SingleTickerProviderStateMixin/ TickerProviderStateMixin mixins at the end of the statement like below:
Eg:
class _ListingViewState extends State with SingleTickerProviderStateMixin { }
just extend with SingleTickerProviderStateMixin in class
you can see here full code

How to Calculate the Coordinates of touch in Flutter?

I want show a popup at touch Coordinates. I am using Stack and Positioned widgets to place the popup.
You can add a GestureDetector as parent of stack, and register onTapDownDetails listener. This should call your listener on every tapdown event, with global offset of the tap in TapDownDetails parameter of the your listener.
Here is sample code demonstrating the same.
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
MyHomePageState createState() => new MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Popup Demo'),
),
body: new MyWidget());
}
}
class MyWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return new MyWidgetState();
}
}
class MyWidgetState extends State<MyWidget> {
double posx = 100.0;
double posy = 100.0;
void onTapDown(BuildContext context, TapDownDetails details) {
print('${details.globalPosition}');
final RenderBox box = context.findRenderObject();
final Offset localOffset = box.globalToLocal(details.globalPosition);
setState(() {
posx = localOffset.dx;
posy = localOffset.dy;
});
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onTapDown: (TapDownDetails details) => onTapDown(context, details),
child: new Stack(fit: StackFit.expand, children: <Widget>[
// Hack to expand stack to fill all the space. There must be a better
// way to do it.
new Container(color: Colors.white),
new Positioned(
child: new Text('hello'),
left: posx,
top: posy,
)
]),
);
}
}
You can simply use a Listener as the parent of your Stack and listen to it's onPointerDown event like so:
Listener(
onPointerDown: (event) {
// use event.localPosition.dx or event.localPosition.dy
},
child: Stack(
children: [
],
),
)

Flutter animation how to fade in/out gradually

I'm starting out on Flutter and trying to make an animation which rotates while fading in and out continuously. So far rotation works, but I'm having difficulties with the fading effect. The widget will gradually become transparent but right after one rotation, it jumps back into opaque before turning transparent again. I'm trying to fix this but I can't seem to find out how. Using .forward() and .reverse() doesn't work, but it's possible I may have implemented the opaque animation incorrectly.
class AnimatedLoader extends AnimatedWidget {
static final _opacityTween = new Tween<double>(begin: 1.0, end: 0.3);
AnimatedLoader({
Key key,
this.alignment: FractionalOffset.center,
Animation<double> turns,
Animation<double> animation,
this.child,
}) : super(key: key, listenable: turns);
Animation<double> get turns => listenable;
final FractionalOffset alignment;
final Widget child;
#override
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
final double turnsValue = turns.value;
final Matrix4 transform = new Matrix4.rotationZ(turnsValue * math.PI * 2.0);
return new Transform(
alignment: alignment,
transform: transform,
child: new Opacity(
opacity: _opacityTween.evaluate(animation),
child: child,
)
);
}
}
class AppLoader extends StatefulWidget {
AppLoaderState createState() => new AppLoaderState();
}
class AppLoaderState extends State<AppLoader> with TickerProviderStateMixin {
AnimationController _controller;
AnimationController _controllerOp;
Animation<double> animation;
#override initState(){
super.initState();
_controller = new AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
)..repeat();
_controllerOp = new AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
animation = new Tween(begin: 0.0, end: 300.0).animate(_controllerOp);
animation.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controllerOp.reverse();
} else if (status == AnimationStatus.dismissed) {
_controllerOp.forward();
}
});
_controllerOp.forward();
}
#override
Widget build(BuildContext context) {
return new Center (
child: new AnimatedLoader(
turns: _controller,
alignment: FractionalOffset.center,
animation: _controllerOp,
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: 150.0,
width: 150.0,
child: new FlutterLogo(),
)
),
);
}
Sorry for the big chunk of code, I'm unsure which part I could've made a mistake in.
I think you're on the right track, but you should only use one AnimationController per AnimatedWidget. I fixed some bugs in your code.
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
Widget build(BuildContext context) {
return new Scaffold(
body: new AppLoader(),
);
}
}
class PulsateCurve extends Curve {
#override
double transform(double t) {
if (t == 0 || t == 1)
return 0.3;
return math.sin(t * math.PI) * 0.35 + 0.65;
}
}
class AnimatedLoader extends AnimatedWidget {
static final _opacityTween = new CurveTween(curve: new PulsateCurve());
AnimatedLoader({
Key key,
this.alignment: FractionalOffset.center,
Animation<double> animation,
this.child,
}) : super(key: key, listenable: animation);
final FractionalOffset alignment;
final Widget child;
#override
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
final Matrix4 transform = new Matrix4.rotationZ(animation.value * math.PI * 2.0);
return new Transform(
alignment: alignment,
transform: transform,
child: new Opacity(
opacity: _opacityTween.evaluate(animation),
child: child,
)
);
}
}
class AppLoader extends StatefulWidget {
AppLoaderState createState() => new AppLoaderState();
}
class AppLoaderState extends State<AppLoader> with TickerProviderStateMixin {
AnimationController _controller;
#override initState() {
super.initState();
_controller = new AnimationController(
duration: const Duration(milliseconds: 1500),
vsync: this,
)..repeat();
}
#override
Widget build(BuildContext context) {
return new Center (
child: new AnimatedLoader(
animation: _controller,
alignment: FractionalOffset.center,
child: new Container(
margin: new EdgeInsets.symmetric(vertical: 10.0),
height: 150.0,
width: 150.0,
child: new FlutterLogo(),
)
),
);
}
}

Flutter - Create a countdown widget

I am trying to build a countdown widget. Currently, I got the structure to work. I only struggle with the countdown itself. I tried this approach using the countdown plugin:
class _Countdown extends State<Countdown> {
int val = 3;
void countdown(){
CountDown cd = new CountDown(new Duration(seconds: 4));
cd.stream.listen((Duration d) {
setState((){
val = d.inSeconds;
});
});
}
#override
build(BuildContext context){
countdown();
return new Scaffold(
body: new Container(
child: new Center(
child: new Text(val.toString(), style: new TextStyle(fontSize: 150.0)),
),
),
);
}
}
However, the value changes very weirdly and not smooth at all. It start twitching. Any other approach or fixes?
It sounds like you are trying to show an animated text widget that changes over time. I would use an AnimatedWidget with a StepTween to ensure that the countdown only shows integer values.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new MyApp(),
));
}
class Countdown extends AnimatedWidget {
Countdown({ Key key, this.animation }) : super(key: key, listenable: animation);
Animation<int> animation;
#override
build(BuildContext context){
return new Text(
animation.value.toString(),
style: new TextStyle(fontSize: 150.0),
);
}
}
class MyApp extends StatefulWidget {
State createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
AnimationController _controller;
static const int kStartValue = 4;
#override
void initState() {
super.initState();
_controller = new AnimationController(
vsync: this,
duration: new Duration(seconds: kStartValue),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.play_arrow),
onPressed: () => _controller.forward(from: 0.0),
),
body: new Container(
child: new Center(
child: new Countdown(
animation: new StepTween(
begin: kStartValue,
end: 0,
).animate(_controller),
),
),
),
);
}
}
The countdown() method should be called from the initState() method of the State object.
class _CountdownState extends State<CountdownWidget> {
int val = 3;
CountDown cd;
#override
void initState() {
super.initState();
countdown();
}
...
Description of initState() from the Flutter docs:
The framework calls initState. Subclasses of State should override
initState to perform one-time initialization that depends on the
BuildContext or the widget, which are available as the context and
widget properties, respectively, when the initState method is called.
Here is a full working example:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:countdown/countdown.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Countdown Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new CountdownWidget();
}
}
class _CountdownState extends State<CountdownWidget> {
int val = 3;
CountDown cd;
#override
void initState() {
super.initState();
countdown();
}
void countdown(){
print("countdown() called");
cd = new CountDown(new Duration(seconds: 4));
StreamSubscription sub = cd.stream.listen(null);
sub.onDone(() {
print("Done");
});
sub.onData((Duration d) {
if (val == d.inSeconds) return;
print("onData: d.inSeconds=${d.inSeconds}");
setState((){
val = d.inSeconds;
});
});
}
#override
build(BuildContext context){
return new Scaffold(
body: new Container(
child: new Center(
child: new Text(val.toString(), style: new TextStyle(fontSize: 150.0)),
),
),
);
}
}
class CountdownWidget extends StatefulWidget {
#override
_CountdownState createState() => new _CountdownState();
}
based on #raju-bitter answer, alternative to use async/await on countdown stream
void countdown() async {
cd = new CountDown(new Duration(seconds:4));
await for (var v in cd.stream) {
setState(() => val = v.inSeconds);
}
}
Why not use a simple TweenAnimationBuilder its easy to use and you don't need to manage any stream controllers or worry about using streams and disposing them off etc;
TweenAnimationBuilder<double>(
duration: Duration(seconds: 10),
tween: Tween(begin: 100.0, end: 0.0),
onEnd: () {
print('Countdown ended');
},
builder: (BuildContext context, double value, Widget child) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Text('${value.toInt()}',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 40)));
}),
here's the dartpad example to playaround
output:
originally answered here
Countdown example using stream, not using setState(...) therefore its all stateless.
this borrow idea from example flutter_stream_friends
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:countdown/countdown.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
static String appTitle = "Count down";
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: appTitle,
theme: new ThemeData(
primarySwatch: Colors.purple,
),
home: new StreamBuilder(
stream: new CounterScreenStream(5),
builder: (context, snapshot) => buildHome(
context,
snapshot.hasData
// If our stream has delivered data, build our Widget properly
? snapshot.data
// If not, we pass through a dummy model to kick things off
: new Duration(seconds: 5),
appTitle)),
);
}
// The latest value of the CounterScreenModel from the CounterScreenStream is
// passed into the this version of the build function!
Widget buildHome(BuildContext context, Duration duration, String title) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: new Center(
child: new Text(
'Count down ${ duration.inSeconds }',
),
),
);
}
}
class CounterScreenStream extends Stream<Duration> {
final Stream<Duration> _stream;
CounterScreenStream(int initialValue)
: this._stream = createStream(initialValue);
#override
StreamSubscription<Duration> listen(
void onData(Duration event),
{Function onError,
void onDone(),
bool cancelOnError}) =>
_stream.listen(onData,
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
// The method we use to create the stream that will continually deliver data
// to the `buildHome` method.
static Stream<Duration> createStream(int initialValue) {
var cd = new CountDown(new Duration(seconds: initialValue));
return cd.stream;
}
}
The difference from stateful is that reload the app will restart counting. When using stateful, in some cases, it may not restart when reload.

Resources