I'm trying to update a widget when I got some data from my database. The widget that I'm trying to change is defined as a class variable:
Widget openFriendRequestNotificationWidget = new Container();
I'm using an empty container because I really don't need to render anything at the beginning and leaving it at null is no option.
I've got two functions, one to create my page and the other one the update my openFriendRequestNotificationWidget:
Widget createFriendsPage() {
if (currentUser.friends == null) {
return new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
openFriendRequestNotificationWidget,
new Material(
child: new InkWell(
child: new Center(
child: new Text("Woops, looks like you have no friends yet.\nTap here to find some!", textAlign: TextAlign.center,),
),
onTap: () => createFriendsDialog(),
)
)
],
);
}
return new Column(
children: <Widget>[
openFriendRequestNotificationWidget,
new Text("ok")
],
);
}
void createReceivedFriendRequestsNotification() {
FirebaseDatabase.instance.reference().child("friend_requests").child(currentUser.uid).once().then((DataSnapshot snap) {
Map<String, Map<String, String>> response = snap.value;
if (response != null) {
this.setState(() {
print("Changing widget");
openFriendRequestNotificationWidget = new Container(
child: new Text("You've got ${response.length.toString()} new friend requests!"),
color: Colors.black,
);
});
}
});
}
The variable is updating in createReceivedFriendRequestsNotification but it is not re-rendering.
Could someone help out?
if you are calling createFriendsPage in initState(), then it means that the code inside initState() is called only once, which is to build the UI.
If it's possible, I suggest that you call your createFriendsPage inside the override method build()
class FriendPage extends StatefullWidget{
//instantiate your state .. }
class FriendsPageState extends State<FriendPage> {
#override
Widget build(Build context) {
return cteateFriendsPage();
}
//other methods here ...
}
Related
I have a StatefulWidget where there is a ListView holding several childs widget.
One of the child is a GridView containing some items.
What I would want to achieve is to rebuild this GridView child when a button is pressed from the Parent widget. The button is located in the bottomNavigationBar in the Parent widget.
However, when I pressed the button, it should go to the _resetFilter() method, which works. But the setState() doesn't seem to update the GridView build() method inside Child widget.
class ParentState extends State<Parent> {
// removed for brevity
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(...),
bottomNavigationBar: BottomAppBar(
child: new Row(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 5.0),
child: SizedBox(
onPressed: () {
_resetFilter();
},
)
),
],
),
),
body: Container(
child: Form(
key: _formKey,
child: ListView(
children: <Widget>[
Column(
children: <Widget>[
Container(
child: Column(
children: <Widget>[
Container(...), // this works
Column(...),
Container(...), // this works
Container(
child: GridView.count(
// ...
children:
List.generate(oriSkills.length, (int i) {
bool isSkillExist = false;
if (_selectedSkills.contains(rc.titleCase)) {
isSkillExist = true;
} else {
isSkillExist = false;
}
return Child( // this doesn't work
id: oriSkills[i]['id'],
name: oriSkills[i]['description'],
skillSelect: isSkillExist, // this boolean showed correct value from the above logic
onChange: onSkillChange,
);
}),
),
),
],
),
)
],
)
],
)),
),
);
}
void _resetFilter() {
setState(() {
_theValue = 0.0;
searchC.text = "";
_selectedSkills = []; // this is the variable that I'd like the GridView to recreate from.
});
}
}
I tried to print one of the field name inside Child widget, but it always showing the old value instead of the new one.
Even after presing the button, it does passing correct value to ChildState.
class ChildState extends State<Child> {
final String name;
final MyCallbackFunction onChange;
bool skillSelect;
double size = 60.0;
ChildState({this.name, this.skillSelect, this.onChange});
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
void setSkillLevel() {
setState(() {
if (skillSelect) {
skillSelect = false;
onChange(name, false);
} else {
skillSelect = true;
onChange(name, true);
}
});
}
Color _jobSkillSelect(bool select) {
print(select); // always print old state instead of new state
return select ? Color(MyColor.skillLvlOne) : Color(MyColor.skillDefault);
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(children: <Widget>[
InkResponse(
onTap: setSkillLevel,
child: Container(
height: size,
width: size,
decoration: BoxDecoration(
image: DecorationImage(
colorFilter: ColorFilter.mode(_jobSkillSelect(skillSelect), BlendMode.color),
),
),
)),
]));
}
}
How can I update the Child widget to have the updated value from the Parent widget after reset button is pressed?
You might want to pass the values to the actual Child class. Not to its state.
The class is whats rebuilding once your parent rebuilds. So the new values will be reflected.
So your Child implementation should look something like this (don't forget to replace the onChange Type to your custom Function.
class Child extends StatefulWidget {
final String name;
final Function(void) onChange;
final bool skillSelect;
final double size;
final Function(bool) onSkillLevelChanged;
const Child({Key key, this.name, this.onChange, this.skillSelect, this.size, this.onSkillLevelChanged}) : super(key: key);
#override
_ChildState createState() => _ChildState();
}
class _ChildState extends State<Child> {
Color _jobSkillSelect(bool select) {
print(select); // always print old state instead of new state
return select ? Color(MyColor.skillLvlOne) : Color(MyColor.skillDefault);
}
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
InkResponse(
onTap: () {
if (widget.onSkillLevelChanged != null) {
widget.onSkillLevelChanged(!widget.skillSelect);
}
},
child: Container(
height: widget.size,
width: widget.size,
decoration: BoxDecoration(
image: DecorationImage(
colorFilter: ColorFilter.mode(_jobSkillSelect(widget.skillSelect), BlendMode.color),
),
),
)),
],
),
);
}
}
In this case the Child ist not responsible anymore for managing its skillSelect property. It simply calls a Function on its parent. The parent then builds with a new skillSelect boolean.
So you might use this child like this:
return Child( // this doesn't work
id: oriSkills[i]['id'],
name: oriSkills[i]['description'],
skillSelect: oriSkills[i]['isSkillExist'],
onChange: onSkillChange,
onSkillLevelChanged: (newSkillLevel) {
setState(() {
oriSkills[i]['isSkillExist'] = newSkillLevel;
});
},
);
I'm trying to change some variables in different methos in Flutter, but the value isn't changed.
An example is something like:
enum UserPlaceStatusType { NONE, GOING, THERE, OUT, CANCELLED }
class PlaceCardState extends State<PlaceCard> {
UserPlaceStatusType _isOtherPlaceActive = UserPlaceStatusType.NONE;
Widget build(BuildContext context) {
return Card(
child: Scaffold(
body: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: this._getBody(),
),
bottomNavigationBar: this._getBottomNavigationBar()));
}
List<Widget> _getBody() {
return [
Expanded(child: Text('test'), flex: 3),
Expanded(child: Text('test'), flex: 6),
Expanded(child: this._getActionsMenu(), flex: 1)
];
}
Widget _getActionsMenu() {
return Container(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
child: IconButton(
icon: Icon(Icons.arrow_forward_ios),
color: Colors.grey[400],
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ListTile(
leading: new Icon(Icons.train),
title: new Text(Utility.format(
Language.of(context).takePlace, [_place.title])),
onTap: () {
showUserStatusDialog<DialogActions>(
context: context,
//It opens a simple dialog
child: this._getCurrentUserPlaceStatus());
},
),
],
);
});
},
));
}
Widget _getCurrentUserPlaceStatus() {
return new GraphqlProvider(
client: new ValueNotifier(
Client(endPoint: 'GraphQLUrl', cache: new InMemoryCache()),
),
child: new Query(
'The GraphQL Query',
variables: {},
builder: ({
bool loading,
var data,
var error,
}) {
if (data != null && data['getCurrentUserPlaceStatus'] != null) {
this._isOtherPlaceActive = UserPlaceStatusType.THERE;
Navigator.pop(context, DialogActions.cancel);
return Container();
} else {
this._isOtherPlaceActive = UserPlaceStatusType.GOING;
Navigator.pop(context, DialogActions.cancel);
return Container();
}
},
));
}
void showUserStatusDialog<T>({BuildContext context, Widget child}) async {
//here there is a validation but the variable value is the initial one, I mean NONE
if (this._isOtherPlaceActive == UserPlaceStatusType.GOING) {
//Cod to do
return;
}
showDialog<T>(
context: context,
builder: (BuildContext context) => child,
).then<void>((T value) {
if (value != null) {
this._isOtherPlaceActive = UserPlaceStatusType.NONE;
Navigator.pop(context);
}
});
}
}
I changed the variable value through some methods, but when I need to apply the validation, that's the initial value, it isn't changed, and I could not apply SetState method cuz it breaks the modal and throws an exception.
I will appreciate any feedback.
The method setState() can't be called inside a widget directly. I'm curious with your use of GrapQLProvider since it returns an empty Container() widget just to check the status of the data.
While I'm unfamiliar with the use of GraphQL, if the client that you're using inherits either a Stream or Future, it can be used to listen when the query is done.
Here's some snippets as demo. Let _testFuture() as the sample for a Future callback.
Future _testFuture() async{
return null;
}
Future can be listened to inside a Widget. When the request finishes, we have the opportunity to call setState().
_testFuture().then((value) {
// Check for values here
setState(() {
// Update values
});
});
Or if the request is set in a Stream, it's also possible to listen for Stream changes inside a Widget.
_streamController.add(_testFuture());
_streamController.stream.listen((event) {
// Check for values here
setState(() {
// Update values
});
});
This may not be the exact answer that you're looking for, but I hope this can guide you for a solution to your approach. I also found a GraphQL sample that uses ObservableQuery as a Stream that you can try.
Your code is very complex and should be refactored. Please notice how dialogs must be called.
enum DialogResult {ok, cancel}
caller_widget.dart
FlatButton(
child: Text('Open dialog'),
onPressed: () async {
// Call dialog and wait for result (async call)
final dialogResult = await showDialog<DialogResult>(
context: context,
builder: (context) => DialogWidget(),
);
if (dialogResult == DialogResult.ok) {
// do something
}
},
),
dialog_widget.dart
...
FlatButton(
child: Text('Ok'),
onPressed: () => Navigator.pop(context, DialogResult.ok), // DialogResult.ok returns
),
FlatButton(
child: Text('Cancel'),
OnPressed: () => Navigator.pop(context, DialogResult.cancel), // DialogResult.cancel returns
),
So you can return required value from dialog and set it to required variable.
P.S. Try to avoid use of old fashion then process of futures and use async/await.
I'm working on a Camera app. I'm using the following Camera plugin - https://github.com/flutter/plugins/tree/master/packages/camera
Here is my working code -
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
List<CameraDescription> cameras;
Future<Null> main() async {
cameras = await availableCameras();
runApp(new MaterialApp(
home: new CameraApp(),
));
}
class CameraApp extends StatefulWidget {
#override
_CameraAppState createState() => new _CameraAppState();
}
class _CameraAppState extends State<CameraApp> {
CameraController controller;
#override
void initState() {
super.initState();
controller = new CameraController(cameras[0], ResolutionPreset.medium);
controller.initialize().then((_) {
if (!mounted) {
return;
}
setState(() {});
});
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
// camera widget
Widget cameraView = new Container(
child: new Row(children: [
new Expanded(
child: new Column(
children: <Widget>[
new AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: new CameraPreview(controller)
)
]
),
)
])
);
return new Scaffold(
body: new Stack(
children: <Widget>[
(!controller.value.initialized) ? new Container() : cameraView,
// ---On top of Camera view add one mroe widget---
],
),
);
}
}
When I'm building the app I'm getting following errors...
I/flutter ( 2097): The following NoSuchMethodError was thrown building CameraApp(dirty, state: _CameraAppState#a0666):
I/flutter ( 2097): The getter 'height' was called on null.
I/flutter ( 2097): Receiver: null
I/flutter ( 2097): Tried calling: height
Even though you have the ternary operator inside the body of the Stack, you are creating the Widget cameraView regardless of whether it is going to be used - so it is being created whether controller.value.initialized is true or false. Adjust the code so that the CameraPreview tree is only built if it is needed, i.e. if initialized is true. For example:
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Stack(
children: <Widget>[
(!controller.value.initialized) ? new Container() : buildCameraView(),
// ---On top of Camera view add one mroe widget---
],
),
);
}
Widget buildCameraView() {
return new Container(
child: new Row(
children: [
new Expanded(
child: new Column(
children: <Widget>[
new AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: new CameraPreview(controller),
),
],
),
),
],
),
);
}
As you suggest in your comment, you can move the ternary operator lower in your build tree, too, and replace just the AspectRatio with an empty Container.
if even after using this check (!controller.value.initialized) ? new Container() : cameraView,still you are getting error that "getter 'height' was called on null",
and the error message is poping on your app only for fraction of second, then it means you are initializing your camera controller in didChangeDependencies()...if yes then use this technique.
bool cameraInitialized = false;
#override
void didChangeDependencies() {
if (cameraInitialized == false) {
final ScreenArguments arguments =
ModalRoute.of(context).settings.arguments;
int cameraIndex = Provider.of<XYZ>(context)
.XX
.firstWhere((element) => element.id == arguments.XX`enter code here`Id)
.cameraIndex;
controller = new CameraController(
widget.cameras[cameraIndex], ResolutionPreset.medium);
controller.initialize().then((value) {
if (!mounted) {
return;
}
setState(() {});
});
setState(() {
cameraInitialized = true;
});
}
super.didChangeDependencies();
}
I've created a flutter app where I'm managing array for todolist in app. I've can add the text by add button.
I've created a widget to show in list.
My question is how am i supposed manage the UI of individual.
Code:
import 'package:flutter/material.dart';
class TodoList extends StatefulWidget {
_TodoListState createState() => new _TodoListState();
}
class _TodoListState extends State<TodoList> {
List _list = new List();
Widget listTile({String data: '[Empty data]'}) {
bool _writable = false;
TextEditingController _textController = new TextEditingController(text: data);
String _text = _textController.text;
if(!_writable){
return new Row(
children: <Widget>[
new Expanded(
child: new Text(data)
),
new IconButton(icon: new Icon(Icons.edit),
onPressed: () {
// setState(() {
_writable = ! _writable;
print(_writable.toString());
// });
}),
new IconButton(icon: new Icon(Icons.remove_circle), onPressed: null),
],
);
} else {
return new Row(
children: <Widget>[
new Expanded(
child: new TextField( controller: _textController )
),
new IconButton(icon: new Icon(Icons.done), onPressed: null),
],
);
}
}
void addInList(String string) {
print(string);
setState(() {
_list.add(string);
});
print(_list);
}
void removeFromList(int index){
}
static final TextEditingController _textController = new TextEditingController();
String get _text => _textController.text;
#override
Widget build(BuildContext context) {
Widget adderTile = new Row(
children: <Widget>[
new Expanded(
child:
new TextField(
textAlign: TextAlign.center,
controller: _textController ,
decoration: new InputDecoration( hintText: 'New item.!' ),
),
),
new IconButton(icon: new Icon(Icons.add), onPressed: (){addInList(_text);}),
],
);
return new MaterialApp(
title: 'TodoList',
home: new Scaffold(
appBar: new AppBar(title: new Text('TodoList'),),
body: new Column(
children: <Widget>[
adderTile,
new ListView.builder(
shrinkWrap: true,
itemCount: _list.length,
itemBuilder: (context, int index){
return listTile(data: _list[index]);
}
),
],
),
),
);
}
}
if i change _writable inside setState then it rerenders widget and _writable becomes false again. if i do it without setState, then _writable becomes true but widget doesn't rerender.
P.S.: i don't want to add another array in to manage which is writable and which is not. Thanks in advance.
The variable
bool _writable = false;
is declared as local variable in the method listTile(), but should be moved next to List _list = new List(); to become a member variable. Then use setState() to set it and rebuild the view.
Edit:
You should create a dedicated StatefulWidget (TodoListEntry), having _writable as member as suggested above. Move almost the whole method body of listTile(...) to the build()-method of the TodoListEntryState, make the parameter String data also a member and pass the value via the constructor.
I want to build a form where I have multiple TextField widgets, and want to have a button that composes and e-mail when pressed, by passing the data gathered from these fields.
For this, I started building an InheritedWidget to contain TextField-s, and based on the action passed in the constructor - functionality not yet included in the code below - it would return a different text from via toString method override.
As I understood, an InheritedWidget holds it's value as long as it is part of the current Widget tree (so, for example, if I navigate from the form it gets destroyed and the value is lost).
Here is how I built my TextForm using InheritedWidget:
class TextInheritedWidget extends InheritedWidget {
const TextInheritedWidget({
Key key,
this.text,
Widget child}) : super(key: key, child: child);
final String text;
#override
bool updateShouldNotify(TextInheritedWidget old) {
return text != old.text;
}
static TextInheritedWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(TextInheritedWidget);
}
}
class TextInputWidget extends StatefulWidget {
#override
createState() => new TextInputWidgetState();
}
class TextInputWidgetState extends State<TextInputWidget> {
String text;
TextEditingController textInputController = new TextEditingController();
#override
Widget build(BuildContext context) {
return new TextInheritedWidget(
text: text,
child: new TextField(
controller: textInputController,
decoration: new InputDecoration(
hintText: adoptionHintText
),
onChanged: (text) {
setState(() {
this.text = textInputController.text;
});
},
),
);
}
#override
String toString({DiagnosticLevel minLevel: DiagnosticLevel.debug}) {
// TODO: implement toString
return 'Név: ' + text;
}
}
And here is the button that launches the e-mail sending:
TextInputWidget nameInputWidget = new TextInputWidget();
TextInheritedWidget inherited = new TextInheritedWidget();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Örökbefogadás'),
),
body: new Container(
padding: const EdgeInsets.all(5.0),
child: new ListView(
children: <Widget>[
new Text('Név:', style: infoText16BlackBold,),
nameInputWidget,
new FlatButton(onPressed: () {
launchAdoptionEmail(nameInputWidget.toString(), 'kutya');
},
child: new Text('Jelentkezem'))
],
),
),
);
}
My problem is that the nameInputWidget.toString() simply returns TextInputWidget (class name) and I can't seem to find a way to access the TextInputWidgetState.toString() method.
I know that TextInheritedWidget holds the text value properly, but I'm not sure how I could access that via my nameInputWidget object.
Shouldn't the TextInputWidget be able to access the data via the context the InheritedWidget uses to determine which Widget to update and store the value of?
This is not possible. Only children of an InheritedWidget can access it's properties
The solution would be to have your InheritedWidget somewhere above your Button. But that imply you'd have to refactor to take this into account.
Following Rémi's remarks, I came up with a working solution, albeit I'm pretty sure it is not the best and not to be followed on a massive scale, but should work fine for a couple of fields.
The solution comes by handling all TextField widgets inside one single State, alongside the e-mail composition.
In order to achieve a relatively clean code, we can use a custom function that build an input field with the appropriate data label, which accepts two input parameters: a String and a TextEditingController.
The label is also used to determine which variable the setState() method will pass the newly submitted text.
Widget buildTextInputRow(var label, TextEditingController textEditingController) {
return new ListView(
shrinkWrap: true,
children: <Widget>[
new Row(
children: <Widget>[
new Expanded(
child: new Container(
padding: const EdgeInsets.only(left: 5.0, top: 2.0, right: 5.0 ),
child: new Text(label, style: infoText16BlackBold)),
),
],
),
new Row(
children: <Widget>[
new Expanded(
child: new Container(
padding: const EdgeInsets.only(left: 5.0, right: 5.0),
child: new TextField(
controller: textEditingController,
decoration: new InputDecoration(hintText: adoptionHintText),
onChanged: (String str) {
setState(() {
switch(label) {
case 'Név':
tempName = 'Név: ' + textEditingController.text + '\r\n';
break;
case 'Kor':
tempAge = 'Kor: ' + textEditingController.text + '\r\n';
break;
case 'Cím':
tempAddress = 'Cím: ' + textEditingController.text + '\r\n';
break;
default:
break;
}
});
}
)),
),
],
)
],
);
}
The problem is obviously that you will need a new TextEditingController and a new String to store every new input you want the user to enter:
TextEditingController nameInputController = new TextEditingController();
var tempName;
TextEditingController ageInputController = new TextEditingController();
var tempAge;
TextEditingController addressInputController = new TextEditingController();
var tempAddress;
This will result in a lot of extra lines if you have a lot of fields, and you will also have to update the composeEmail() method accordingly, and the more fields you have, you will be more likely to forget a couple.
var emailBody;
composeEmail(){
emailBody = tempName + tempAge + tempAddress;
return emailBody;
}
Finally, it is time to build the form:
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Örökbefogadás'),
),
body: new ListView(
children: <Widget>[
buildTextInputRow('Név', nameInputController),
buildTextInputRow('Kor', ageInputController),
buildTextInputRow('Cím', addressInputController),
new FlatButton(onPressed: () { print(composeEmail()); }, child: new Text('test'))
],
),
);
}
For convenience, I just printed the e-mail body to the console while testing
I/flutter ( 9637): Név: Zoli
I/flutter ( 9637): Kor: 28
I/flutter ( 9637): Cím: Budapest
All this is handled in a single State.