This question already has answers here:
Error: The instance member ... can't be accessed in an initializer
(4 answers)
Closed 11 months ago.
Looking to get some clarity as this code is driving me crazy. I am passing data between screens in flutter using an object. Here is what my model file looks like
models.dart
class CompInfo<Map> {
String uid;
String email;
String companyName;
String companyAddress;
bool needLandscapingHelp;
bool needLawnServicesHelp;
CompInfo(
{this.uid = '',
this.email = '',
this.companyName = '',
this.companyAddress = '',
this.needLandscapingHelp=false,
this.needLawnServicesHelp=false,};
}
Screen one: user enters company name and address
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => CompanyProject(
companyinfo: sendCompanyInfo(
companyName.text, comapanyAddress.text))));
helper function on screen 1
CompInfo(compName, compAddress) {
CompInfo compinfo =
CompInfo(companyName: compName, companyPitch: compPitch);
return compinfo;
}
Screen 2:
class CompanyProject extends StatefulWidget {
CompanyInfo companyinfo;
CompanyProject({Key? key, required this.companyinfo}) : super(key: key);
#override
State<CompanyProject> createState() => _CompanyProjectState();
}
class _CompanyProjectState extends State<CompanyProject> {
#override
void initState() {
compinfo=widget.companyinfo;
super.initState();
}
late CompanyProject compinfo;
TextEditingController tellusmore = TextEditingController();
bool mvpBoxyes = false;
bool mvpBoxno = false;
bool errorprompt = false;
List checkboxlist = [
CheckBoxState(
title: 'Lawn',
icon: Icons.computer,
),
CheckBoxState(
title: 'Garden', icon: Icons.smartphone, widgettitle: compinfo.needLandscapingHelp),
]
etc...
I'm basically creating checkboxes and would like the model to update with the value. However the widget title: compinfo.needLandscapingHelp keeps throwing "The instance member 'compinfo' can't be accessed in an initializer.
Try replacing the reference to the instance member with a different expression"
Any ideas?
You have to assign compinfo value either inside initState or inside a function, the reason for this is that instance members of the class cannot be re-assign inside the class initializer, so you have to assign it after the class has been initialize which is after the initState lifecycle:
class _CompanyProjectState extends State<CompanyProject> {
#override
void initState() {
compinfo=widget.companyinfo;
checkboxlist = [
CheckBoxState(
title: 'Lawn',
icon: Icons.computer,
),
CheckBoxState(
title: 'Garden', icon: Icons.smartphone, widgettitle: compinfo.needLandscapingHelp),
]
super.initState();
}
late CompanyProject compinfo;
TextEditingController tellusmore = TextEditingController();
bool mvpBoxyes = false;
List checkboxlist;
bool mvpBoxno = false;
bool errorprompt = false;
I made it possible to toggle the heart icon in the post like Instagram.
And I implemented it so that user information is uploaded to the firestore when I toggle the icon.
There is no problem with the 'Like' and 'Unlike' function of 'Like Icon', but there is one other problem.
There is a problem that the color of the icon does not change immediately when toggling the icon in the post. I can check that this icon has changed when I go to a different screen and come back in.
The following is the code for this icon. (I edited this code)
class LikeToggleIcon extends StatefulWidget {
final String postKey;
final PostModel postModel;
const LikeToggleIcon(
{Key key,
this.postKey,
this.postModel,
this.fromSearch,
this.searchResults})
: super(key: key);
#override
State<LikeToggleIcon> createState() => _LikeToggleIconState();
}
class _LikeToggleIconState extends State<LikeToggleIcon> {
// bool _isLiked = false;
#override
Widget build(BuildContext context) {
//get userModel
UserModel userModel =
Provider.of<UserModelState>(context, listen: false).userModel;
return IconButton(
onPressed: () {
// setState(() {
postNetworkRepo.toggleLike(
widget.postModel.postKey, userModel.userKey);
// });
},
icon: Icon(
widget.postModel.numOfLikes.contains(userModel.userKey)
? Icons.favorite_outlined
: Icons.favorite_border_outlined,
size: 27,
color: Colors.redAccent,
),
);
//toggle method
class PostNetworkRepo with Transformers {
Future<void> toggleLike(String postKey, String userKey) async {
final DocumentReference postRef =
Firestore.instance.collection(COLLECTION_POSTS).document(postKey);
final DocumentSnapshot postSnapshot = await postRef.get();
//check Post collection
if (postSnapshot.exists) {
//check already contain userKey
//if no contain upload userKey, else delete userKey (toggle Like/Unlike)
if (postSnapshot.data[KEY_NUMOFLIKES].contains(userKey)) {
postRef.updateData({
KEY_NUMOFLIKES: FieldValue.arrayRemove([userKey])
});
} else {
postRef.updateData({
KEY_NUMOFLIKES: FieldValue.arrayUnion([userKey])
});
}
}
}
}
PostNetworkRepo postNetworkRepo = PostNetworkRepo();
//This is the part of detail post screen.
class DetailScreen extends StatefulWidget {
#override
_DetailScreenState createState() => _DetailScreenState();
}
class _DetailScreenState extends State<DetailScreen> {
#override
Widget build(BuildContext context) {
//get userModel
UserModel userModel =
Provider.of<UserModelState>(context, listen: false).userModel;
return Scaffold(
appBar: AppBar(
title: _postUser(),
actions: [
//toggle likeIcon
LikeToggleIcon(
postKey: widget.postKey,
postModel: widget.postModel,
),
],
),
I tried using setState() on the IconButton(), but the problem was not solved.
can I get help with this problem?
I believe you are using setState() to change value of variable that's inside the Widget and NOT the State of that widget.
You need to have variables that changes the Widget inside the state for it to be updated. Keeping it inside Widget won't change the Icon, unless the whole widget is recreated (like when you change the screen).
(correct me if I'm wrong. Providing full code of the widget could be more useful)
I am trying to make that when I submit the button, the isLoading variable is set to true, and when the block issues the state it returns to false, to show a circular progress indicator.
My problem is that I don't know WHERE or HOW to put that variable, nor how to tell the bloc that this variable is true before / outside the BlocBuilder,
this is the code of my button:
TextButton(
child: Text("submit"),
onPressed: () {
context
.read<ShowMusicHomeBloc>()
.add(OnSearchTracks(q: _searchText.text));
_searchText.clear();
},
),
Could you give me a practical example how I can set this variable isLoading to true using flutter_bloc?
You can make a LoadingState and emit it when you press the button.
abstract class MyState {}
class MyLoadingState extends MyState {}
class MyLoadedState extends MyState {
final MyData data;
MyLoadedState(this.data);
}
// other states like ErrorState or InitialState
class MyCubit extends Cubit<MyState>{
MyCubit(this.someService) : super(MyLoadingState());
final SomeService someService;
Future<void> fetchData(){
emit(MyLoadingState);
final fetchedData = someService.fetchData();
emit(MyLoadedState(fetchedData));
}
}
you can apply the same concept to Blocs too.
I am trying the following code and while hot reloading it is incrementing static variable checkIfIncremented variable. Please someone explain me why is it so ??
import 'package:flutter/material.dart';
void main()=>runApp(MaterialApp(
home: TrialApp(),
));
class TrialApp extends StatefulWidget {
#override
_TrialAppState createState() => _TrialAppState();
}
class _TrialAppState extends State<TrialApp> {
static int checkIfIncremented = 0;
#override
Widget build(BuildContext context) {
checkIfIncremented++;
return Scaffold(
body: Center(
child: Text("This variable is getting incremented after each hot reload : $checkIfIncremented"),
),
);
}
}
This problem is due to the fact that each time you hot reload your program, the build method runs automatically. so you should avoid using checkIfIncremented++; inside this function.
I am not sure why you use this code and what is your purpose, but you can use this code if you want to incrementcheckIfIncremented only at the first load:
bool firstLoad = true;
#override
void didChangeDependencies() {
super.didChangeDependencies();
if(firstLoad){
checkIfIncremented++;
firstLoad = false;
setState((){});
}
}
Basically I am trying to make an app whose content will be updated with an async function that takes information from a website, but when I do try to set the new state, it doesn't reload the new content. If I debug the app, it shows that the current content is the new one, but after "rebuilding" the whole widget, it doesn't show the new info.
Edit: loadData ( ) method, basically read a URL with http package, the URL contains a JSON file whose content changes every 5 minutes with new news. For example a .json file with sports real-time scoreboards whose scores are always changing, so the content should always change with new results.
class mainWidget extends StatefulWidget
{
State<StatefulWidget> createState() => new mainWidgetState();
}
class mainWidgetState extends State<mainWidget>
{
List<Widget> _data;
Timer timer;
Widget build(BuildContext context) {
return new ListView(
children: _data);
}
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) async {
String s = await loadData();
this.setState(() {
_data = <Widget> [new childWidget(s)];
});
});
}
}
class childWidget extends StatefulWidget {
childWidget(String s){
_title = s;
}
Widget _title;
createState() => new childState();
}
class childState extends State<gameCardS> {
Widget _title;
#override
Widget build(BuildContext context) {
return new GestureDetector(onTap: foo(),
child: new Card(child: new Text(_title));
}
initState()
{
super.initState();
_title = widget._title;
}
}
This should sort your problem out. Basically you always want your Widgets created in your build method hierarchy.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(new MaterialApp(home: new Scaffold(body: new MainWidget())));
class MainWidget extends StatefulWidget {
#override
State createState() => new MainWidgetState();
}
class MainWidgetState extends State<MainWidget> {
List<ItemData> _data = new List();
Timer timer;
Widget build(BuildContext context) {
return new ListView(children: _data.map((item) => new ChildWidget(item)).toList());
}
#override
void initState() {
super.initState();
timer = new Timer.periodic(new Duration(seconds: 2), (Timer timer) async {
ItemData data = await loadData();
this.setState(() {
_data = <ItemData>[data];
});
});
}
#override
void dispose() {
super.dispose();
timer.cancel();
}
static int testCount = 0;
Future<ItemData> loadData() async {
testCount++;
return new ItemData("Testing #$testCount");
}
}
class ChildWidget extends StatefulWidget {
ItemData _data;
ChildWidget(ItemData data) {
_data = data;
}
#override
State<ChildWidget> createState() => new ChildState();
}
class ChildState extends State<ChildWidget> {
#override
Widget build(BuildContext context) {
return new GestureDetector(onTap: () => foo(),
child: new Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0),
child: new Card(
child: new Container(
padding: const EdgeInsets.all(8.0),
child: new Text(widget._data.title),
),
),
)
);
}
foo() {
print("Card Tapped: " + widget._data.toString());
}
}
class ItemData {
final String title;
ItemData(this.title);
#override
String toString() {
return 'ItemData{title: $title}';
}
}
This was really giving me headache and no Google results were working. What finally worked was so simple. In your child build() assign the value to the local variable before you return. Once I did this everything worked with subsequent data loads. I even took out the initState() code.
Many thanks to #Simon. Your answer somehow inspired me to try this.
In your childState:
#override
Widget build(BuildContext context) {
_title = widget._title; // <<< ADDING THIS HERE IS THE FIX
return new GestureDetector(onTap: foo(),
child: new Card(child: new Text(_title));
}
Hopefully this works in your code. For me, I use a Map for the entire JSON record passed in, rather than a single String, but that should still work.
The Root issue explained
initState(), for the child widget, is called only once when the Widget is inserted into the tree. Because of this, your child Widget variables will never be updated when they change on the parent widget. Technically the variables for the widgets are changing, you are just not capturing that change in your state class.
build() is the method that gets called every time something in the Widget changes. This is the reason #gregthegeek solution works. Updating the variables inside the build method of your child widget will ensure they get the latest from parent.
Works
class ChildState extends State<ChildWidget> {
late String _title;
#override
Widget build(BuildContext context) {
_title = widget._title; // <==== IMPORTANT LINE
return new GestureDetector(onTap: () => foo(),
child: new Text(_title),
);
}
}
Does not work
(It will not update when _title changes in parent)
class ChildState extends State<ChildWidget> {
late String _title;
#override
void initState() {
super.initState();
_title = widget._title; // <==== IMPORTANT LINE
}
#override
Widget build(BuildContext context) {
return new GestureDetector(onTap: () => foo(),
child: new Text(_title),
);
}
}
I'm unsure why this happens when calling setState(...) in an async function, but one simple solution is to use:
WidgetsBinding.instance.addPostFrameCallback((_) => setState(...));
instead of just setState(...)
This fixed my issue... If you have an initial value to be assigned on a variable use it in initState()
Note : Faced this issue when I tried to set initial value inside build function.
#override
void initState() {
count = widget.initialValue.length; // Initial value
super.initState();
}
don't use a future within a future; use different function that will return each future individually like this
List<Requests> requestsData;
List<DocumentSnapshot> requestsDocumentData;
var docId;
#override
void initState() {
super.initState();
getRequestDocs();
}
Future<FirebaseUser> getData() {
var _auth = FirebaseAuth.instance;
return _auth.currentUser();
}
getRequestDocs() {
getData().then((FirebaseUser user) {
this.setState(() {
docId = user.uid;
});
});
FireDb()
.getDocuments("vendorsrequests")
.then((List<DocumentSnapshot> documentSnapshots) {
this.setState(() {
requestsDocumentData = documentSnapshots;
});
});
for (DocumentSnapshot request in requestsDocumentData) {
this.setState(() {
requestsData.add(Requests(
request.documentID,
request.data['requests'],
Icons.data_usage,
request.data['requests'][0],
"location",
"payMessage",
"budget",
"tokensRequired",
"date"));
});
}
}
you can create individual functions for
FireDb().getDocuments("vendorsrequests")
.then((List<DocumentSnapshot> documentSnapshots) {
this.setState(() {
requestsDocumentData = documentSnapshots;
});
});
and
for (DocumentSnapshot request in requestsDocumentData) {
this.setState(() {
requestsData.add(Requests(
request.documentID,
request.data['requests'],
Icons.data_usage,
request.data['requests'][0],
"location",
"payMessage",
"budget",
"tokensRequired",
"date"));
});
}
I found that the use of
this
with setState is must
The real issue on child StatefulWidget not rebuilding is in the KEY
Hey, I'm a bit late to the discussion, but I think this is important.
I was facing a similar problem a while back and I even came to this thread to get some ideas.
In my case, I was simply getting widget.value directly inside the build method of the childWidget, and it was not updating when i called setState in the mainWidget.
Then i found this video: https://youtu.be/kn0EOS-ZiIc
(When to Use Keys - Flutter Widgets 101 Ep. 4) -
Here the Google dev talks about how keys in Flutter.
The short answer is
In a StatefulWidget the actual value you pass is stored in the state, not in the widget itself, like a StatelessWidget does.
When you call setState in the mainWidget, Flutter walks down the widget tree and checks each childWidget's type and key, to see if anything has changed. As stateful widgets store their values in the state, Flutter thinks the child widgets did not change (because the types and keys are the same) and does not rebuild them, even if the value changed.
The real solution is to give the widget a key containing the value that is changing, so when Flutter is walking down the tree, it notices that the key changed, and rebuilds the stateful widget.
Other solutions here may work as well, but if you want to really understand it, this video is worth watching.
first check whether it is a stateless or stateful widget,and if the class is stateless then make it to a stateful widget and try adding a code after closing the
setState(() { _myState = newValue; });
In my case, it was just defining the state as a class property and not a local variable in the build method
Doing this -
List<Task> tasks = [
Task('Buy milk'),
Task('Buy eggs'),
Task('Buy bread'),
];
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) => TaskTile(
...
instead of this -
#override
Widget build(BuildContext context) {
List<Task> tasks = [
Task('Buy milk'),
Task('Buy eggs'),
Task('Buy bread'),
];
return ListView.builder(
itemBuilder: (context, index) => TaskTile(
...
Found the best solution.
If you are using a stateless widget you can't use set state, so just convert the stateless widget to statefull widget