`vsync` property in TabController constructor - dart

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

Related

How to maintain Flutter Global BloC state using Provider on Hot Reload?

I seem to lose application state whenever I perform a hot reload.
I am using a BloC provider to store application state. This is passed at the App level in the main.dart and consumed on a child page. On the initial load of the view, the value is shown. I can navigate around the application and the state persists. However, when I perform a hot reload, I lose the values and seemingly the state.
How can I fix this issue so that state is preserved on Hot Reload?
Bloc Provider
abstract class BlocBase {
void dispose();
}
class BlocProvider<T extends BlocBase> extends StatefulWidget {
BlocProvider({
Key key,
#required this.child,
#required this.bloc,
}): super(key: key);
final T bloc;
final Widget child;
#override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context){
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
return provider.bloc;
}
static Type _typeOf<T>() => T;
}
class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>{
#override
void dispose(){
widget.bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context){
return widget.child;
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return BlocProvider<ApplicationStateBloc>(
bloc: ApplicationStateBloc(),
child: MaterialApp(
title: 'Handshake',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: LoadingPage(),
)
);
}
}
class ProfileSettings extends StatefulWidget {
#override
_ProfileSettingsState createState() => _ProfileSettingsState();
}
class _ProfileSettingsState extends State<ProfileSettings>{
ApplicationStateBloc _applicationStateBloc;
#override
void initState() {
super.initState();
_applicationStateBloc = BlocProvider.of<ApplicationStateBloc>(context);
}
#override
void dispose() {
_applicationStateBloc?.dispose();
super.dispose();
}
Widget emailField() {
return StreamBuilder<UserAccount>(
stream: _applicationStateBloc.getUserAccount,
builder: (context, snapshot){
if (snapshot.hasData) {
return Text(snapshot.data.displayName, style: TextStyle(color: Color(0xFF151515), fontSize: 16.0),);
}
return Text('');
},
);
}
#override
Widget build(BuildContext context) {
return BlocProvider<ApplicationStateBloc>(
bloc: _applicationStateBloc,
child: Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: <Widget>[
emailField(),
.... // rest of code
class ApplicationStateBloc extends BlocBase {
var userAccountController = BehaviorSubject<UserAccount>();
Function(UserAccount) get updateUserAccount => userAccountController.sink.add;
Stream<UserAccount> get getUserAccount => userAccountController.stream;
#override
dispose() {
userAccountController.close();
}
}
I was facing the same problem. Inherited widgets make it hard disposing bloc's resources.
Stateful widget, on the other hand, allows disposing, but in the implementation you're using it doesn't persist the bloc in the state causing state loss on widgets rebuild.
After some experimenting I came up with an approach that combines the two:
class BlocHolder<T extends BlocBase> extends StatefulWidget {
final Widget child;
final T Function() createBloc;
BlocHolder({
#required this.child,
#required this.createBloc
});
#override
_BlocHolderState createState() => _BlocHolderState();
}
class _BlocHolderState<T extends BlocBase> extends State<BlocHolder> {
T _bloc;
Function hello;
#override
void initState() {
super.initState();
_bloc = widget.createBloc();
}
#override
Widget build(BuildContext context) {
return BlocProvider(
child: widget.child,
bloc: _bloc,
);
}
#override
void dispose() {
_bloc.dispose();
super.dispose();
}
}
Bloc holder creates bloc in createState() and persists it. It also disposes bloc's resources in dispose().
class BlocProvider<T extends BlocBase> extends InheritedWidget {
final T bloc;
const BlocProvider({
Key key,
#required Widget child,
#required T bloc,
})
: assert(child != null),
bloc = bloc,
super(key: key, child: child);
static T of<T extends BlocBase>(BuildContext context) {
final provider = context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider;
return provider.bloc;
}
#override
bool updateShouldNotify(BlocProvider old) => false;
}
BlocProvider, as the name suggests, is only responsible for providing the bloc to nested widgets.
All the blocs extend BlocBase class
abstract class BlocBase {
void dispose();
}
Here's a usage example:
class RouteHome extends MaterialPageRoute<ScreenHome> {
RouteHome({List<ModelCategory> categories, int position}): super(builder:
(BuildContext ctx) => BlocHolder(
createBloc: () => BlocMain(ApiMain()),
child: ScreenHome(),
));
}
You are losing the state because your bloc is being retrieved in the _ProfileSettingsState's initState() thus, it won't change even when you hot-reload because that method is only called only once when the widget is built.
Either move it to the build() method, just before returning the BlocProvider
#override
Widget build(BuildContext context) {
_applicationStateBloc = BlocProvider.of<ApplicationStateBloc>(context);
return BlocProvider<ApplicationStateBloc>(
bloc: _applicationStateBloc,
child: Scaffold(
backgroundColor: Colors.white,
....
or to the didUpdateWidget method which is called anytime the widget state is rebuild.
Have in mind that if you are using a non-broadcast stream in your bloc you may get an exception if you try to listen to a stream that is already being listened to.

Flutter does not update subwidget

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
))
],
),

get height of a Widget using its GlobalKey in flutter

I am struggling getting the height of a Widget using its GlobalKey.
the function that is getting the height is called after the Layout is rendered to make sure the context is available but key.currentState and also key.currentContext still returns null.
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget{
#override
State<StatefulWidget> createState() => new TestPageState();
}
class TestPageState extends State<TestPage>{
final TestWidget testWidget = new TestWidget();
#override
initState() {
//calling the getHeight Function after the Layout is Rendered
WidgetsBinding.instance
.addPostFrameCallback((_) => getHeight());
super.initState();
}
void getHeight(){
final GlobalKey key = testWidget.key;
//returns null:
final State state = key.currentState;
//returns null:
final BuildContext context = key.currentContext;
//Error: The getter 'context' was called on null.
final RenderBox box = state.context.findRenderObject();
print(box.size.height);
print(context.size.height);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: testWidget,
);
}
}
class TestWidget extends StatefulWidget{
#override
Key get key => new GlobalKey<TestWidgetState>();
#override
State<StatefulWidget> createState() => new TestWidgetState();
}
class TestWidgetState extends State<TestWidget>{
#override
Widget build(BuildContext context) {
return new Container(
child: new Text("Test", style: const TextStyle(fontSize: 32.0, fontWeight: FontWeight.bold),),
);
}
}
You need to assign that key to a widget using super in the widget constructor. Not add it as a field.
That Key also must be constant.
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget {
#override
State<StatefulWidget> createState() => new TestPageState();
}
class TestPageState extends State<TestPage> {
final key = new GlobalKey<TestWidgetState>();
#override
initState() {
//calling the getHeight Function after the Layout is Rendered
WidgetsBinding.instance.addPostFrameCallback((_) => getHeight());
super.initState();
}
void getHeight() {
//returns null:
final State state = key.currentState;
//returns null:
final BuildContext context = key.currentContext;
//Error: The getter 'context' was called on null.
final RenderBox box = state.context.findRenderObject();
print(box.size.height);
print(context.size.height);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new TestWidget(key: key),
);
}
}
class TestWidget extends StatefulWidget {
TestWidget({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => new TestWidgetState();
}
class TestWidgetState extends State<TestWidget> {
#override
Widget build(BuildContext context) {
return new Container(
child: new Text(
"Test",
style: const TextStyle(fontSize: 32.0, fontWeight: FontWeight.bold),
),
);
}
}
Define your constructor like this:
const MyWidget(GlobalKey key) : super(key:key);.
The framework stores the BuildContext and State object in the Widget.key field which is passed into the object by constructor instead of an arbitary key field.
Sometimes better approach is to use LayoutBuilder. In this case the code looks like
return LayoutBuilder(
builder: (context, constraints) {
print('Available container sizes - ${constraints.maxWidth} - ${constraints.maxHeight}');
return Container(
child: new Text(
"Test",
style: const TextStyle(fontSize: 32.0, fontWeight: FontWeight.bold),
),
);
},
);
This approach gives info dynamically, often useful with animations.

Flutter change main appbar title on other pages

Looking for some assistance with changing the AppBar title on subsequent pages, so me being tabs and some not. MyApp is defined on the authentication page of my app. I then goto a new page that holds the tabs, then I have other pages off some of the tab pages, what I want to be able to do is, instead of putting another AppBar under the main one, I just want to change the title of the main AppBar when I am on any of the other pages.
Any ideas how to do this, I saw 1 example that did not fit because my tabs are setup different and could not make it fit, thought maybe there was a way to define the title initially so that I can change state or something and change the title.
Any ideas or thoughts on this?
You can add a TabController and add listen to it such that you call setState whenever you are switching between the Tabs, and change the AppBar title accordingly.
import "package:flutter/material.dart";
void main(){
runApp(new MaterialApp(home:new MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin{
final List<MyTabs> _tabs = [new MyTabs(title: "Teal",color: Colors.teal[200]),
new MyTabs(title: "Orange",color: Colors.orange[200])
];
MyTabs _myHandler ;
TabController _controller ;
void initState() {
super.initState();
_controller = new TabController(length: 2, vsync: this);
_myHandler = _tabs[0];
_controller.addListener(_handleSelected);
}
void _handleSelected() {
setState(() {
_myHandler= _tabs[_controller.index];
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text(_myHandler.title),
backgroundColor: _myHandler.color,
bottom: new TabBar(
controller: _controller,
tabs: <Tab>[
new Tab(text: _tabs[0].title,),
new Tab(text: _tabs[1].title,)
],
),),
);
}
}
class MyTabs {
final String title;
final Color color;
MyTabs({this.title,this.color});
}
With the little help of above answer, I could write a simple and beginner friendly code.
The concept is easy, get current tab index and change title with set state. You need to "detect" when the active tab is changed. That is why we add listener (the one who detects CHANGE, AND ACTS)
Let me know in comments if below code doesn't make sense
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
TabController _tcontroller;
final List<String> titleList = ["Home Page", "List Page", "Message Page"];
String currentTitle;
#override
void initState() {
currentTitle = titleList[0];
_tcontroller = TabController(length: 3, vsync: this);
_tcontroller.addListener(changeTitle); // Registering listener
super.initState();
}
// This function is called, every time active tab is changed
void changeTitle() {
setState(() {
// get index of active tab & change current appbar title
currentTitle = titleList[_tcontroller.index];
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(currentTitle),
centerTitle: true,
bottom: TabBar(
controller: _tcontroller,
tabs: <Widget>[
Icon(Icons.home),
Icon(Icons.format_list_bulleted),
Icon(Icons.message),
],
),
),
body: TabBarView(
controller: _tcontroller,
children: <Widget>[
Center(child: Text(titleList[0])),
Center(child: Text(titleList[1])),
Center(child: Text(titleList[2])),
],
),
);
}
}

ExpansionTile doesn't keep state

following problem:
I have a list of ExpansionTiles which works very well. The only problem I'm facing is that a expanded ExpansionTile which is scrolled out of view will, after scrolling it into view again, no longer be expanded. This leads to undesired user experience and also a kind of "jumpy" scrolling.
The documentation states the following:
When used with scrolling widgets like ListView, a unique key must be specified to enable the ExpansionTile to save and restore its expanded state when it is scrolled in and out of view.
This doesn't work though. So far I have found no way to make this work.
Here is the code so far:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'ExpansionTile Test',
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Widget> _getChildren() {
List<Widget> elements = [];
for (int i = 0; i < 20; i++) {
elements.add(new ListChild());
}
return elements;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('ExpansionTile Test'),
),
body: new ListView(
children: _getChildren(),
),
);
}
}
class ListChild extends StatefulWidget {
#override
State createState() => new ListChildState();
}
class ListChildState extends State<ListChild> {
GlobalKey<ListChildState> _key = new GlobalKey();
#override
Widget build(BuildContext context) {
return new ExpansionTile(
key: _key,
title: const Text('Test Tile'),
children: <Widget>[
const Text('body'),
],
);
}
}
Use a PageStorageKey instead of a GlobalKey.

Resources