The problem I'm having is that although I'm successfully updating a value of my meter (FlutterGauge) widget via setState, the widget itself does not reflect that change. I know that the rebuild is happening, and that the value on the meter widget is indeed being updated.
void updateScore(bool isOnTopic) {
//for the purposes of testing we won't use isOnTopic (its a hardcoded true anyway)
setState(() {
meterScore += 15;
});
print("Score Updated:");
print("--------" + meterScore.toString() + "--------");
}
#override
Widget build(BuildContext context) {
print('+++++++We\'re building! Using this.meterScore value: ' +
meterScore.toString() +
'+++++++');
//Replacing my FlutterGauge and return with the line below as a way to isolate the FlutterGauge widget as the problem
//return Center(child: Text(this.meterScore.toString()));
FlutterGauge meter = new FlutterGauge(
circleColor: meterColor,
secondsMarker: SecondsMarker.none,
hand: Hand.short,
number: Number.none,
width: 200,
index: meterScore,
fontFamily: "Iran",
counterStyle: TextStyle(color: Colors.black, fontSize: 35),
counterAlign: CounterAlign.center,
isDecimal: false);
print("------------Accessing meter.index: " +
meter.index.toString() +
"----------------");
return meter;
}
I'm fairly certain that the issue is how I'm using the FlutterGauge widget from the flutter_gauge package because when I replace it with a simple Text widget, and feed my value to it, it will update and reflect the update as expected.
That being the case, here is a link to flutter_gauge for reference:
https://pub.dev/packages/flutter_gauge#-readme-tab-
I'm pretty new to flutter and this is my first stackoverflow question, so apologies if I'm making any obvious mistakes. Thanks in advance!
Just in case there's important code I'm leaving out that you may need to see, here is the entire file:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_gauge/flutter_gauge.dart';
class Meter extends StatefulWidget {
Meter({Key key}) : super(key: key);
MeterState createState() => MeterState();
}
class MeterState extends State<Meter> {
#override
initState() {
super.initState();
}
double meterScore = 75;
Color meterColor = Colors.green;
void updateMeterColor() {
setState(() {
meterColor = Colors.green;
});
}
void updateScore(bool isOnTopic) {
//for the purposes of testing we won't use isOnTopic (its a hardcoded true anyway)
setState(() {
meterScore += 15;
});
print("Score Updated:");
print("--------" + meterScore.toString() + "--------");
}
#override
Widget build(BuildContext context) {
print('+++++++We\'re building! Using this.meterScore value: ' +
meterScore.toString() +
'+++++++');
//Replacing my FlutterGauge and return with the line below as a way to isolate the FlutterGauge widget as the problem
//return Center(child: Text(this.meterScore.toString()));
FlutterGauge meter = new FlutterGauge(
circleColor: meterColor,
secondsMarker: SecondsMarker.none,
hand: Hand.short,
number: Number.none,
width: 200,
index: meterScore,
fontFamily: "Iran",
counterStyle: TextStyle(color: Colors.black, fontSize: 35),
counterAlign: CounterAlign.center,
isDecimal: false);
print("------------Accessing meter.index: " +
meter.index.toString() +
"----------------");
return meter;
}
#override
void dispose() {
print("---------We are disposing (as intended)------------");
super.dispose();
}
}
EDITS:
here is my terminal after a hot restart and initial visit:
I/flutter (11573): +++++++We're building! Using this.meterScore value: 75.0+++++++
I/flutter (11573): ------------Accessing meter.index: 75.0----------------
I/flutter (11573): 75.0
invoking the function once:
I/flutter (11573): Score Updated:
I/flutter (11573): --------90.0--------
I/flutter (11573): +++++++We're building! Using this.meterScore value: 90.0+++++++
I/flutter (11573): ------------Accessing meter.index: 90.0----------------
I updated the code snippets (removed the this. from all meterScore, added a comment addressing the function's unused argument)
I should probably mention that the updateScore function is being called outside the file. As I said, the function itself seems to work fine as the print statements indicate.
here is where i'm using the widget (this is the entire file):
class RecordingPage extends StatefulWidget {
RecordingPage({Key key}) : super(key: key);
_RecordingPageState createState() => _RecordingPageState();
}
class _RecordingPageState extends State<RecordingPage> {
final GlobalKey<MeterState> meterState = GlobalKey<MeterState>();
int yes = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: Container(
width: this.yes * 10.0,
child: FittedBox(
child: FloatingActionButton(
onPressed: null,
backgroundColor: Colors.white,
))),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
backgroundColor: Colors.white,
appBar: offTopTitle,
body: Column(
children: <Widget>[
Row(children: [
Expanded(
child: FlatButton(
child: Text("Go To Websocket"),
color: Colors.blue,
onPressed: () {
Navigator.pushNamed(context, WebsocketRoute);
},
),
),
]),
Container(
height: MediaQuery.of(context).size.height / 4,
child: Image.asset('assets/placeholderWave.gif'),
),
Container(
margin: EdgeInsets.only(top: 5),
height: MediaQuery.of(context).size.height / 4,
child: Meter(key: meterState),
),
Recorder((isOnTopic) {
meterState.currentState.updateScore(isOnTopic);
}),
],
),
bottomNavigationBar: AppBarBuilder());
}
}
After toying around with the package in a new flutter project I have been unable to get it to update after creation using many different ways and after looking at all available documentation for the package at this time, I am not sure it is meant to be used in this way. There are no examples of changing the gauge after it is built so it appears to be an immutable widget.
I did some digging and the syncfusion_flutter_gauges looks like a promising alternative and this article goes over how to do what I think you are attempting in your project. Hope it helps
I think I found the solution. This worked for me. Open flutter_gauge.dart and remove all the commented section. Reinstall back your app and refresh your data. This steps works for me.
Related
I have a situation where I have a listview of containers, and I would like when I double tap each container another container pops up below with information. Currently what I am trying to do is wrap each container within a column and do something like:
onDoubleTap() {showBox = true}, and in the column have code:
children: [post(), showbox == true? infobox() : container()] but I am not sure of the correct implementation. Any help would be great!
you should maintain a list of containers:
class ContainerAdder extends StatefulWidget {
const ContainerAdder({Key? key}) : super(key: key);
#override
_ContainerAdderState createState() => _ContainerAdderState();
}
class _ContainerAdderState extends State<ContainerAdder> {
List<Widget> containers = <Widget>[];
Random random = Random();
List<Color> colors = [
Colors.blue,
Colors.green,
Colors.red,
Colors.orange,
Colors.purple,
Colors.pink,
Colors.teal,
Colors.yellow,
];
addContainer() {
setState(() {
int r = random.nextInt(colors.length);
containers.add(
InkWell(
onDoubleTap: () => addContainer(),
child: Container(
margin: const EdgeInsets.only(bottom: 1.0),
height: 50.0,
color: colors[r],
),
),
);
});
}
#override
void initState() {
super.initState();
addContainer();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [...containers],
),
);
}
}
As you can notice, the method addContainer() adds a container which is wrapped in an InkWell to have the tap listener. The doubleTap calls the method addContainer().
I simply spread the containers list inside ListView widget.
In the addContainer() method, I wrap the codes inside setState() so as to refresh the tree. You can use any other state management architecture if you so wish.
For the first time, I call addContainer() inside initState(), in order to populate the list with the first element.
Actually, I have a parent widget, and It has some of the child widgets in its Column.
like this
Container(
width: double.infinity,
color: Colors.white,
padding: EdgeInsets.fromLTRB(20.0, 50.0, 20.0, 0.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Title(),
SizedBox(height: 80.0,),
confirmLoginType(),
SizedBox(height: 10.0),
LoginButton(),
PolicyTips(
key: IndexGlobalKey.policyTipsKey,
updateState: receiveMessageUpdateState
),
Bottom()
],
),
),
in the PolicyTips, I pass a key to it, and I want to get the key in the LoginButton , but It has always been null when I get currentState.
The code is below:LoginButton
class LoginButton extends StatefulWidget {
LoginButton({Key key}) : super(key: key);
#override
_LoginButtonState createState() => _LoginButtonState();
}
class _LoginButtonState extends State<LoginButton> {
#override
Widget build(BuildContext context) {
policyTipsKey = IndexGlobalKey.policyTipsKey.currentState;
return Container(
child: Text()
)
}
}
what can I do? help me please, thanks.
This is IndexGlobalKey code.
class IndexGlobalKey {
static final GlobalKey<_PolicyTipsState> policyTipsKey = GlobalKey<_PolicyTipsState>();
static GlobalKey<_FormState> phoneLoginKey = GlobalKey<_FormState>();
static GlobalKey<_FormForIdCardLoginState> idCardLoginKey = GlobalKey<_FormForIdCardLoginState>();
}
Build method of _LoginButtonState runs before PolicyTips renders and before IndexGlobalKey.policyTipsKey is actually set. The reason is LoginButton goes before PolicyTips in column. Thats why you get null when you call IndexGlobalKey.policyTipsKey.currentState from build of _LoginButtonState.
To solve this you need to call IndexGlobalKey.policyTipsKey.state right where you use it. For example, when you need to get policy tips state on button tap just use it inside onPressed callback:
class _LoginButtonState extends State<LoginButton> {
#override
Widget build(BuildContext context) {
// An example of your button
return TextButton(
onPressed: () {
final policyTipsState = IndexGlobalKey.policyTipsKey.currentState;
// Here you can use policyTipsState
},
child: Text('button'),
);
}
}
I have a TabBarView with two tabs in main widget. First tab includes gridview with cards. Cards use parent widget (MyHomePage) as listener to listen in-card button clicks.
When i click on button in some card, listener impl. must open second Tab and pass selected Excursion to it. But when I do it, at first iteration, ExcursionEditor(currentExcursion) says, that argument is null, but parent build says, that it is not. If I resize my browser, it calls global rebuild and currentExcursion reach last build value.
So, i cant understand, why MyHomePage build doesn't affect on TabBarView content with arguments passed by constructor
class MyHomePage
import 'package:flutter/material.dart';
import 'package:questbuilder/api/content_manager.dart';
import 'package:questbuilder/model/excursion.dart';
import 'package:questbuilder/pages/tab_editor.dart';
import 'package:questbuilder/pages/tab_my_excursions.dart';
import 'package:questbuilder/widgets/excursion_preview_card.dart';
import 'package:logger/logger.dart';
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with TickerProviderStateMixin
implements ExcursionCardInteractionListener {
Logger logger = Logger();
Excursion currentExcursion;
TabController tabController;
#override
void initState() {
super.initState();
print("INIT STATE FOR HOME PAGE");
tabController = TabController(vsync: this, length: 2);
}
#override
Widget build(BuildContext context) {
var screenSize = MediaQuery.of(context).size;
print("HOME PAGE BUILD currentExcursion = ${currentExcursion?.toJson()}");
return Scaffold(
extendBodyBehindAppBar: true,
appBar: PreferredSize(
preferredSize: Size(screenSize.width, 1000),
child: Container(
color: Colors.black,
child: Padding(
padding: EdgeInsets.fromLTRB(10, 10, 30, 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(children: [
Padding(
padding: EdgeInsets.fromLTRB(10, 0, 10, 10),
child: Text('QUESTBUILDER',
style: TextStyle(color: Colors.white))),
SizedBox(width: screenSize.width / 20),
Container(
width: screenSize.width / 6,
child: TabBar(
labelPadding: EdgeInsets.fromLTRB(10, 0, 10, 10),
indicatorColor: Colors.white,
controller: tabController,
tabs: [
Tab(text: "Мои экскурсии"),
Tab(text: "Редактор"),
]))
]),
Padding(
padding: EdgeInsets.fromLTRB(0, 0, 0, 10),
child: Row(
children: [
FlatButton.icon(
label: Text("Создать экскурсию"),
icon: Icon(Icons.add),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40.0)),
textColor: Colors.white,
color: Colors.green,
onPressed: () {
createExcursion();
}),
SizedBox(
width: 40,
),
InkWell(
onTap: () {},
child: Text(
'Вход',
style: TextStyle(color: Colors.white),
),
)
],
)),
],
),
),
),
),
body: Padding(
padding: EdgeInsets.all(15),
child: TabBarView(
controller: tabController,
children: [
// Set listener to cards in this widget to prerform 'edit' clicks
MyExcursionsTab(this),
ExcursionEditor(currentExcursion)
],
)));
}
// Here i call setState from cards
#override
void editExcursion(Excursion excursion) {
setState(() {
currentExcursion = excursion;
});
tabController.animateTo(1);
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
void createExcursion() {
ContentManager.client.createExcursion(0).then((value) {
currentExcursion = value;
editExcursion(currentExcursion);
});
}
}
class ExcursionEditor
import 'dart:typed_data';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:questbuilder/api/content_manager.dart';
import 'package:questbuilder/model/excursion.dart';
import 'package:questbuilder/model/excursion_content.dart';
import 'package:questbuilder/model/excursion_data.dart';
import 'package:questbuilder/model/picture.dart';
class ExcursionEditor extends StatefulWidget {
Excursion excursion;
ExcursionEditor(this.excursion);
#override
State<StatefulWidget> createState() => ExcursionEditorState();
}
class ExcursionEditorState extends State<ExcursionEditor> {
ExcursionData currentData;
ExcursionContent currentContent;
Excursion excursion;
List<Picture> pictures = [];
#override
void initState() {
super.initState();
print("INIT EDITOR widget.excrusion = ${widget.excursion?.toJson()}");
// At this point, after call setState() in HomePage widget.excrusion is always null
// until I resize browser, thereby calling global state reset
//
if (widget.excursion != null)
ContentManager.client.getPictureList(widget.excursion.id).then((value) {
pictures.addAll(value);
print(pictures);
});
}
#override
Widget build(BuildContext context) {
excursion = widget.excursion;
print("BUILD EDITOR excursion = ${widget.excursion?.toJson()}");
return excursion != null
? Container()
: Container(
child: Align(
alignment: Alignment.center,
child: Text("Выберите экскурсию для редактирования")));
}
}
Log of first launch and card click build sequence:
HOME PAGE BUILD currentExcursion = null
HOME PAGE BUILD currentExcursion = {id: 1}
INIT EDITOR widget.excrusion = null
BUILD EDITOR excursion = null
After browser window resize
HOME PAGE BUILD currentExcursion = {id: 1}
BUILD EDITOR excursion = {id: 1}
BUILD EDITOR excursion = {id: 1}
HOME PAGE BUILD currentExcursion = {id: 1}
BUILD EDITOR excursion = {id: 1}
After screen resize problem still appear, just replacing null value in editor with old Excursion. New clicks on cards doesn't have effect, setState in callback still not update.
I've tried to bind it on static stream listeners, on TabController listener - it just look like TabBarView late for 1 build cycle of arguments update. Maybe there are some similar questions, but i've done all from thouse answers and got nothing
I am not really sure, but it seems like race condition between setState and _tabController.animateTo(1); because they both try to rebuild the child ExcursionEditor(currentExcursion)
If you print the excursion in ExcursionEditor constructor, you will see the updated value. But at the end the value not reach the build function.
The simple workaround is changing editExcursion to the async function and add a small delay between this 2 actions. Otherwise you can try to use other way to pass data between widgets (like provider)
#override
Future editExcursion(Excursion excursion) async {
setState(() {
currentExcursion = excursion;
});
await Future.delayed(Duration(milliseconds:50));
tabController.animateTo(1);
}
I will post my projects minimum classes here that you can reproduce the faulty behavior.
The listing of the classes here goes mostly from the top of the flutter widget hierarchy down the rest...
main.dart
import 'package:TestIt/widgets/applicationpage.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
final ApplicationPage applicationPage =
ApplicationPage(title: 'Flutter Demo');
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
primarySwatch: Colors.blue,
),
home: applicationPage);
}
}
applicationpage.dart
import 'package:flutter/material.dart';
import 'body.dart';
class ApplicationPage extends StatefulWidget {
ApplicationPage({Key key, this.title}) : super(key: key);
final String title;
#override
_ApplicationPageState createState() => _ApplicationPageState();
}
class _ApplicationPageState extends State<ApplicationPage> {
final Body body = new Body();
#override
Widget build(BuildContext context) {
return Scaffold(
body: body);
}
}
body.dart
import 'package:TestIt/viewmodels/excercise.dart';
import 'package:TestIt/viewmodels/workout.dart';
import 'package:flutter/material.dart';
import 'Excercises/ExcerciseListWidget.dart';
class Body extends StatelessWidget {
#override
Widget build(BuildContext context) {
var workouts = new List<Workout>();
var pullDay = new Workout("Pull day", new List<Excercise>());
workouts.add(pullDay);
return Container(
margin: EdgeInsets.all(5),
child: DefaultTabController(
// Added
length: workouts.length, // Added
initialIndex: 0, //Added
child: Scaffold(
appBar: PreferredSize(
// todo: add AppBar widget here again
preferredSize: Size.fromHeight(50.0),
child: Row(children: <Widget>[
TabBar(
indicatorColor: Colors.blueAccent,
isScrollable: true,
tabs: getTabs(workouts),
),
Container(
margin: EdgeInsets.only(left: 5.0),
height: 30,
width: 30,
child: FloatingActionButton(
heroTag: null,
child: Icon(Icons.add),
backgroundColor: Colors.red,
foregroundColor: Colors.white,
elevation: 5.0,
onPressed: () => print("add workout"))),
Container(
margin: EdgeInsets.only(left: 5.0),
height: 30,
width: 30,
child: FloatingActionButton(
heroTag: null,
child: Icon(Icons.remove),
backgroundColor: Colors.red,
foregroundColor: Colors.white,
elevation: 5.0,
onPressed: () => print("add workout"))),
])),
body: TabBarView(
children: getTabViews(workouts),
),
)));
}
List<ExcerciseListWidget> getTabViews(List<Workout> workouts) {
var tabViews = new List<ExcerciseListWidget>();
for (var i = 0; i < workouts.length; i++) {
tabViews.add(ExcerciseListWidget(workouts[i].excercises));
}
return tabViews;
}
List<Tab> getTabs(List<Workout> workouts) {
Color textColor = Colors.blueAccent;
return workouts
.map((w) => new Tab(
child: Text(w.name, style: TextStyle(color: textColor)),
))
.toList();
}
}
ExcerciseListWidget.dart
import 'package:TestIt/viewmodels/excercise.dart';
import 'package:flutter/material.dart';
import 'ExcerciseWidget.dart';
class ExcerciseListWidget extends StatefulWidget {
ExcerciseListWidget(this.excercises);
final List<Excercise> excercises;
#override
_ExcerciseListWidgetState createState() => _ExcerciseListWidgetState();
}
class _ExcerciseListWidgetState extends State<ExcerciseListWidget> {
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
widget.excercises.insert(
0,
new Excercise(widget.excercises.length + 1, "test",
widget.excercises.length * 10));
});
},
child: Icon(Icons.add),
backgroundColor: Colors.red,
foregroundColor: Colors.white,
elevation: 5.0,
),
body: Container(
padding: const EdgeInsets.all(2),
child: ReorderableListView(
onReorder: (index1, index2) => {
print("onReorder"),
},
children: widget.excercises
.map((excercise) => ExcerciseWidget(
key: ValueKey(excercise.id), excercise: excercise))
.toList())));
}
}
ExcerciseWidget.dart
import 'package:TestIt/viewmodels/excercise.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'ExcerciseDetailsWidget.dart';
class ExcerciseWidget extends StatefulWidget {
ExcerciseWidget({this.key, this.excercise}) : super(key: key);
final Excercise excercise;
final Key key;
#override
_ExcerciseWidgetState createState() => _ExcerciseWidgetState();
}
class _ExcerciseWidgetState extends State<ExcerciseWidget> {
#override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: 3.0, bottom: 3.0),
// TODo: with this ink box decoration the scrolling of the excercises goes under the tabbar... but with the ink I have a ripple effect NOT under
// the element...
child: Ink(
decoration: BoxDecoration(
borderRadius: new BorderRadius.all(new Radius.circular(5.0)),
border: Border.all(color: Colors.orange),
color: Colors.green),
child: InkWell(
onTap: () => {navigateToEditScreen(context)},
child: Column(
children: <Widget>[
Container(
color: Colors.red, child: Text(widget.excercise.name)),
],
)),
));
}
navigateToEditScreen(BuildContext context) async {
final Excercise result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ExcerciseDetailsWidget(excercise: widget.excercise)));
setState(() {
widget.excercise.name = result.name;
});
}
}
ExcerciseDetailsWidget.dart
import 'package:TestIt/viewmodels/excercise.dart';
import 'package:flutter/material.dart';
class ExcerciseDetailsWidget extends StatefulWidget {
final Excercise excercise;
ExcerciseDetailsWidget({Key key, #required this.excercise}) : super(key: key);
#override
_ExcerciseDetailsWidgetState createState() => _ExcerciseDetailsWidgetState();
}
class _ExcerciseDetailsWidgetState extends State<ExcerciseDetailsWidget> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.excercise.name),
),
body: Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 2, top: 2),
child: Form(
key: _formKey,
child: Column(children: <Widget>[
new RaisedButton(
elevation: 2,
color: Colors.blue,
child: Text('Save'),
onPressed: () {
setState(() {
widget.excercise.name = "new name";
});
Navigator.pop(context, widget.excercise);
}),
TextFormField(
decoration: InputDecoration(
//hintText: 'excercise name',
labelText: 'Excercise name',
),
initialValue: widget.excercise.name,
),
]))));
}
}
workout.dart
import 'excercise.dart';
class Workout{
Workout(this.name, this.excercises);
String name;
List<Excercise> excercises;
}
excercise.dart
class Excercise {
int id;
Excercise(this.id,this.name, this.restBetweenSetsInSeconds);
String name;
int restBetweenSetsInSeconds;
}
How to reproduce the faulty behavior to get the exception:
Click on the bottom-right floating action button to create an excercise test stub which is added to the only existing workout.
Click the newly added excercise
The ExcerciseDetailsWidget is loaded
Click Save in the ExcerciseDetailsWidget
Navigation goes back to the Initial screen and the Exception hits you in the face bam!
Exception
FlutterError (setState() called after dispose(): _ExcerciseWidgetState#bccdb(lifecycle state: defunct, not mounted)
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().)
Question
Why is the formerly added and clicked ExcerciseWidget`s State disposed when I returned from the ExcerciseDetailsWidget ?
Check for is mounted and then call setState is no solution because in any case the excercise should NOT be disposed because I have to update it with the new excercise name.
If you know a flutter online site where I can put the project I will do so please let me know!
I am a flutter beginner maybe I do something completely wrong bear that in mind :-)
UPDATE
What I have done to workaround the problem is:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ExcerciseDetailsWidget(excercise: widget.excercise)));
do not await the result of the Navigator.
Instead I do this in the Screen2:
onPressed: () {
if (_formKey.currentState.validate()) {
// WHY can I set here the new text WITHOUT setState but when I navigated back the new excercise name is reflected in the list of excercises. Actually that should not be the case right? That confuses me totally.
widget.excercise.name =
excerciseNameTextController.value.text;
Navigator.pop(context);
}
},
but this is really just a workaround that works in this special EDIT use case.
When I have an ADD use case I need to return something to add it to the list of excercises...
Could it be that the problem is that I await the result inside the excercise?
I guess I will try to await the result excercise on the context/level of the ExercerciseListWidget not inside the ExcerciseWidget.
UPDATE 2
Reading more about the navigator it seems or could be that when I am navigating back to the former route which is my initial/root that all the knowledge about the clicked excercise is gone? Do I need therefore kind of nested routing? like "/workouts/id/excercises/id" ?
Despite the downvotes, this is a legitimate question. After poking around a little bit, the reason seems to be the ReorderableListView. For some reason, even if you are providing keys to each child of the list, when the ReorderableListView is rebuilt, all of its children are disposed and reinitialized. Because of this, when you navigate back from ExcerciseDetailsWidget, you are calling setState within a state that has been disposed - this is why you are getting that specific exception.
Frankly, your current code makes it very difficult to figure out whether it's something you've done wrong or a bug related to ReorderableListView. The only thing that can be said for sure is that replacing the ReorderableListView with a regular ListView will fix it.
I highly recommend cleaning up your code first - my IDE lit up like a Christmas tree when I copied your code in. Get rid of the new keyword. Use const constructors. Fix the Excercise typo that repeats itself 60 times in 250 rows of code.
And most importantly, given that you are mutating and displaying a data object across multiple stateful widgets, start using Provider for state management.
I created a method that returns a list of Widget using this following code and would like to handle onPressed events individually! (i.e change background color of clicked button)
I'm new to Flutter and can't find a way to do this!
List<Widget> workingHoursButtons() {
List<Widget> timeButtons = [];
for (var i = 8; i <= 17; i++) {
timeButtons.add(
Padding(
padding: const EdgeInsets.all(8.0),
child: SizedBox(
width: 59.0,
height: 50.0,
child: FlatButton(
color: i == currentHour ? Color(0xff425660) : null,
shape: RoundedRectangleBorder(
side: BorderSide(),
borderRadius: BorderRadius.circular(3.0),
),
child: Text(
"$i",
style: TextStyle(
color: i == currentHour ? Colors.white : null,
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
),
onPressed: i < currentHour
? null
: () {
print(i);
},
),
),
),
);
}
return timeButtons;
}
So, based on Igor’s comment, yes, you’ll need a customizable StatefulWidget that can hold a unique id per button so that it can be checked against your current hour. A simplified example:
First, setting up your button list:
workingHoursButtons() {
buttonList = new List();
for (var i = 0; i <= 5; i++) {
buttonList.add(MyButton(index: i, whatHour: _currentHour,));
}
}
What your main build Widget might look like, using a ListView.builder:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: ...,
body: ListView.builder(
itemCount: buttonList.length,
itemBuilder: (BuildContext context, int index) {
return buttonList[index];
},
),
);
}
And your custom button widget, passing it an index and your currentHour:
class MyButton extends StatefulWidget {
MyButton({Key key, this.index, this.whatHour}) : super(key: key);
final int index;
final int whatHour;
#override
_MyButtonState createState() => _MyButtonState();
}
class _MyButtonState extends State<MyButton> {
Color _btnColor;
#override
void initState() {
super.initState();
_btnColor = _setInitColor();
}
Color _setInitColor() {
return widget.index == widget.whatHour ? Color(0xff425660) : null;
}
MaterialColor _changeColor() {
// just testing with blue/red colors
return widget.index < widget.whatHour ? Colors.blue : Colors.red;
}
#override
Widget build(BuildContext context) {
return FlatButton(
color: _btnColor,
child: Text('button' + widget.index.toString()),
onPressed: () {
setState(() {
_btnColor = _changeColor();
});
},
);
}
}
Modify as needed.
You need to use StatefulWidget and store colors for each button in a state of your widget. Then, using onPressed event, you can change color for this button (by passing its index) by calling setState method and your changed button will repaint.
I want to thank Igor and TWL for their answers but one thing i've noticed regarding this kind of issue is that there's no "correct" answer. For anyone who'll face this similar issue in the future, i would summarize it in this way.
You'll need a variable that will change for every button press(I had to create 4 in my case)
Call setState() for every change (i.e button press)
Of course depending on your dynamic buttons logic this can get messy but that seems to be the only solution.
On a side note: I think there should a better way to handle button press(using this.?) or maybe it's just me expecting flutter to work like JS