I'm working on a flutter search bar where the results from the buildSuggestions are used to populate the buildResults widget. The example from the flutter tutorial (boring flutter show) generates a string. I want to be able to select and print from the string both id and season. I tried substring but that won't always work since the colors have different length.
import 'package:flutter/material.dart';
final colors = [
{'id': 'red', 'season': 'winter',},
{'id': 'green', 'season': 'spring',}
];
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: ListView.builder(
itemBuilder: (context, index) => ListTile (
title: Card(
child: Text (colors[index].toString()))),
itemCount: colors.length))),
);}
}
OUTPUT
{id: blue, season: winter}
{id: green, season: spring}
Thanks.
Colors[index] will give out the content of the item at that index since that is a list. The data at the index however is a map, hence why you get the "{id: blue, season: winter}"
Try using this code for your Text widget as this extracts the data from the maps that are within your list.
Text("${colors[index]['id']} and ${colors[index]['season']}")
Related
I watched a video tutorial for learning flutter and I encountered an error using the map method, can you help me fix it?
This is the link of the video I watched and followed along : https://www.youtube.com/watch?v=x0uinJvhNxI&t=18425s
This is the code :
import 'package:flutter/material.dart';
import './question.dart';
import './answer.dart';
class Quiz extends StatelessWidget {
final List<Map<String, Object>> questions;
final int questionIndex;
final Function answerQuestion;
Quiz(\{required this.questions, required this.answerQuestion,required this.questionIndex});
#override
Widget build(BuildContext context) {
return Column(
children: [
Question(
questions[questionIndex]['questionText'] as String,
/* answerQuestion(Named function) is a pointer that is passed as a value to onPressed but not the result of the function name ;*/ // onpressed attribute requires a function
),
...(questions[questionIndex]['answers'] as List<Map<String,Object>>).map(answer){ //Creating a list of widgets
return Answer(() => answerQuestion(answer['score']),answer['text']); // Return a widget
}.toList() // Based on the old List, it creates a new List
],
);
}
}
So i am trying to retrieve data from my firestore document which is an email
I am not understanding how i can save the data as a string when i retrieve it from firestore.
help pls.
Getting error: type 'Future is not subtype of type 'String' on result: getEmail()
Ps: im new to flutter
my code:
getEmail() async{
String _email = (await FirebaseAuth.instance.currentUser()).email;
return _firestore.collection('users').document(_email).collection('met_with').document('email').get();
}
...
children: <Widget>[
BottomSheetText(question: 'Email', result:getEmail()),
SizedBox(height: 5.0),
....
..document('email)'.get() will actually return Future<DocumentSnapshot>. So getEmail() doesn't return a String.
You need to get the data out of the DocumentSnapshot:
Future<String> getEmail() async {
String _email = (await FirebaseAuth.instance.currentUser()).email;
DocumentSnapshot snapshot = await _firestore.collection('users')
.document(_email)
.collection('met_with')
.document('email')
.get();
// print("data: ${snapshot.data}"); // might be useful to check
return snapshot.data['email']; // or another key, depending on how it's saved
}
For more info, check out the documentation in the API reference
Now, besides that, getEmail() is an async function. Calling it from your widget tree is probably not how you want to handle this.
You'll need to wait for the result of getEmail() before you can use it in your UI. A FutureBuilder might help:
children: <Widget>[
FutureBuilder<String>(
future: getEmail(),
builder: (context, snapshot) {
if(!snapshot.hasData) {
return CircularProgressIndicator(); // or null, or any other widget
}
return BottomSheetText(question: 'Email', result: snapshot.data);
},
),
SizedBox(height: 5.0),
... //other
],
I am using a formfield widget from flutter
https://pub.dartlang.org/packages/datetime_picker_formfield
However, I want to pass the date that the user entered back to my previous widget (addReminder) but can't get to do that.
I have tried putting it in a static variable and accessing it but that did not work out, I am weak with dart so couldn't get the classes to have a mutual variable that I can use through initializing the widget as an object with a specific variable
And tried to get the variable through a getter but failed to do so.
The parent widget that calls the class dateTime:
class addReminder extends StatelessWidget{
dateTimeWidget = new dateTime();
DateTime date;
#override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: Text('Add a Reminder'),
),
body:
new Container(
padding: new EdgeInsets.all(20.0),
child: new Form(
child: new ListView(
children: <Widget>[
new TextFormField(
keyboardType: TextInputType.text, // Use email input type for emails.
decoration: new InputDecoration(
hintText: 'Title of reminder',
),
),
dateTimeWidget,
RaisedButton(
child: Text('Save'),
onPressed:(){
//This is where I want to extract the date and save it to a local variable (date in this case)
Navigator.pop(context);
},
)
],
),
),
),
);
}
}
The DateTime widget that uses 'DateTimePickerFormField' with both the widget and state class:
import 'package:flutter/material.dart';
import 'package:datetime_picker_formfield/datetime_picker_formfield.dart';
import 'package:intl/intl.dart';
class dateTime extends StatefulWidget{
#override
dateTimeState createState() => dateTimeState();
}
class dateTimeState extends State<dateTime>{
static DateTime dateT;
InputType inputType = InputType.both;
final formats = {
InputType.both: DateFormat("EEEE, MMMM d, yyyy 'at' h:mma"),
InputType.date: DateFormat('yyyy-MM-dd'),
InputType.time: DateFormat("HH:mm"),
};
Widget build(BuildContext context) => Container(
child: DateTimePickerFormField(
inputType: InputType.both,
editable: true,
format: formats[inputType],
decoration: InputDecoration(
labelText: 'Date/Time', hasFloatingPlaceholder: false),
onChanged: (dt) => setState(() => dateT = dt),
)
);
}
I later on attempted this :
Added this method to the addReminder class:
dateTimee(BuildContext context) async {
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context)=> dateTime()),
);
}
and called it this way :
dateTimee(context),
and in the dateTime class in the onchanged parameter I added
onChanged: (dt) {
setState(() => dateT = dt);
Navigator.of(context).pop(dateT);
},
However I get the error :
I/flutter (18662): Another exception was thrown:
'package:flutter/src/widgets/navigator.dart': Failed assertion: line1995
pos 12: '!_debugLocked': is not true.
I think it's because the method is of Future type where the call should be to a widget
So I don't know what to do
The widget I am calling which is dateTimee is based on calling the widget dateTimePickerFromField
Have a look at the cookbook https://flutter.io/docs/cookbook/navigation/returning-data
In summary: Navigator.push returns a future that will complete when the pushed widget calls Navigator.pop. You can pass a return value to pop to be the value that the future resolves with.
// In widget 1..
final result = await Navigator.of(context).push(...);
// In widget 2..
Navigator.of(context).pop('output');
Then once widget 2 calls pop, the future that widget 1 is awaiting will complete, and the result variable will be assigned to the 'output' string.
I have the following situation:
The main widget with Row 1,2,3
In Row 2, I initially set the Widget A
What I want:
replace the Widget A with Widget B once I have a state change in Widget A
handle the replacement in Widget A and not in the main Widget (so I don't want to use callbacks from Widget A or observing a global state and react on it in the main Widget)
Replacement means: Widget B is still a child of the main Widget (in my case stays in the second row and doesn't go fullscreen as it is the case when you use routes/Navigator?)
My reasoning for what I want:
in each row of the main widget the user can interact with a sub menue which e.g. in row 2 consists of WidgetA -> [user interaction] -> WidgetB [user interaction] -> WidgetC
I don't want to manage all this different states from the main widget
What I tried:
//in Widget A -> in order to switch to Widget B
Navigator.push(context, new MaterialPageRoute(
builder: (_) => new WidgetB(),
));
This doesn't do the job because the Widget B doesn't stay in the WidgetTree of the main Widget
If this is not possible I would like to know what is the flutter way of achieving what I want :-)
You need to have some parent that knows of this subnavigation. You can't really replace a view by itself, its parent is what has the reference to that widget in the tree, so the parent is what needs to convey this to flutter.
That said, you can make a custom widget whose job is to listen for child events, and change the child accordingly.
App
/ \
Row Row
|
WidgetParent
|
WidgetA
Here WidgetParent could keep e.g. a Listenable and pass a ChangeNotifier to WidgetA. When WidgetA decides WidgetX should come to screen it can emit that to the parent.
class WidgetParent extends AnimatedWidget {
WidgetParent() : super(listenable: ValueNotifier<Widget>(null));
ValueNotifier<Widget> get notifier => listenable as ValueNotifier<Widget>;
Widget build(BuildContext context) {
return new Center(child: notifier.value ?? WidgetA(notifier));
}
}
Now child widgets can notify who's next.
class WidgetA extends StatelessWidget {
WidgetA(this.notifier);
final ValueNotifier<Widget> notifier;
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () => notifier.value = WidgetB(notifier),
child: Text("This is A, go to B"),
);
}
}
class WidgetB extends StatelessWidget {
WidgetB(this.notifier);
final ValueNotifier<Widget> notifier;
#override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () => notifier.value = WidgetA(notifier),
child: Text("This is B, go to A"),
);
}
}
I am fairly new to Flutter but one workaround I could think is - You use State with the widget. For example, if you want to display two rows depending on what use does then you set height of one row what you desire and set height of second row to zero. And swap it on demand.
Of course, you need to empty the contents as well. e.g. setting text to '' etc.
This may not be the ideal solution but could work.
I would like to have a text field whose value always reflects that of a certain field in a given object. I thought Bindable might be the way to do this. However, using the following example:
#!/usr/bin/env groovy
import groovy.swing.SwingBuilder
import groovy.beans.Bindable
import static javax.swing.JFrame.EXIT_ON_CLOSE
class TextModel {
#Bindable String text
}
def textModel = new TextModel()
def builder=new SwingBuilder()
builder.build {
frame( title: 'Binding Example (Groovy)', size: [240,100], show: true,
locationRelativeTo: null, defaultCloseOperation: EXIT_ON_CLOSE ) {
gridLayout cols: 1, rows: 2
textField id: 'textField'
bean textModel, text: bind{ textField.text }
label text: bind{ textModel.text }
}
}
textModel.text="AAAA"
modified from:
http://groovy.codehaus.org/Bindable+and+Vetoable+transformation
only the label text is set to that of textModel, but not that of the textField.
Any ideas???
Thank you
Misha
p.s. I seem to be able to get the opposite behavior, where the TextField reflects that state of the variable, but its value is not updated, if I do:
#!/usr/bin/env groovy
import groovy.swing.SwingBuilder
import groovy.beans.Bindable
import static javax.swing.JFrame.EXIT_ON_CLOSE
class TextModel {
#Bindable String text
}
def textModel = new TextModel()
def builder=new SwingBuilder()
builder.build {
frame( title: 'Binding Example (Groovy)', size: [240,100], show: true,
locationRelativeTo: null, defaultCloseOperation: EXIT_ON_CLOSE ) {
gridLayout cols: 1, rows: 2
textField id: 'textField',text:bind{ textModel.text }
label text: bind{ textModel.text }
}
}
textModel.text="AAAA"
p.p.s. If I add both:
#!/usr/bin/env groovy
import groovy.swing.SwingBuilder
import groovy.beans.Bindable
import static javax.swing.JFrame.EXIT_ON_CLOSE
class TextModel {
#Bindable String text
}
def textModel = new TextModel()
def builder=new SwingBuilder()
builder.build {
frame( title: 'Binding Example (Groovy)', size: [240,100], show: true,
locationRelativeTo: null, defaultCloseOperation: EXIT_ON_CLOSE ) {
gridLayout cols: 1, rows: 2
textField id: 'textField',text:bind{ textModel.text }
bean textModel, text: bind{ textField.text }
label text: bind{ textModel.text }
}
}
textModel.text="AAAA"
I get
Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Attempt to mutate in notification
p.p.p.s. This is my best solution:
#!/usr/bin/env groovy
import groovy.swing.SwingBuilder
import groovy.beans.Bindable
import static javax.swing.JFrame.EXIT_ON_CLOSE
class TextModel {
#Bindable String text
}
def textModel = new TextModel()
textModel.text="AAAA"
def builder=new SwingBuilder()
builder.build {
frame( title: 'Binding Example (Groovy)', size: [240,100], show: true,
locationRelativeTo: null, defaultCloseOperation: EXIT_ON_CLOSE ) {
gridLayout cols: 1, rows: 2
textField id: 'textField',text:textModel.text
bean textModel, text: bind{ textField.text }
label text: bind{ textModel.text }
}
}
The Griffon guide on binding, describes the mutual property as being what you want. Even though you're not using Griffon in this case, bind seems to be a standard Groovy feature. If you create textField like this:
textField id: 'textField', text: bind('text', source: textModel, mutual: true)
textField will get its initial value from textModel.text, write updates to it when the user types in the field, and display the updated value when changes to textModel.text occur (from some background thread, say). When I tried to bind two text inputs like this, I started getting the IllegalStateExceptions you described, but it seems one input and multiple labels are fine.