Flutter SteamBuilder snapshot.data is null - flutter

I am new to flutter. I am trying to use SteamBuilder to retrieve snapshot data.
But snapshot.data is returning null.
snapshot.connectionState == ConnectionState.done
is false.
snapshot.hasData
is false.
This is the dart file in which I am trying to retrieve snapshot data
return StreamBuilder<UserData>(
stream: DatabaseService(uid: user.uid).userData,
builder: (context, snapshot) {
//print(snapshot.connectionState == ConnectionState.done);
//print(snapshot.data);
if(snapshot.hasData){
print('snapshot has data');
UserData userData = snapshot.data;
return Form(
key: _formKey,
child: Column(
children: <Widget>[
Text(
'Update your brew settings.',
style: TextStyle(fontSize: 18.0),
),
SizedBox(height: 20.0),
TextFormField(
initialValue: userData.name,
decoration: textInputDecorator,
validator: (val) => val.isEmpty ? 'Please enter a name' : null,
onChanged: (val) => setState(() => _currentName = val),
),
SizedBox(height: 10.0),
DropdownButtonFormField(
value: _currentSugars ?? userData.sugars,
decoration: textInputDecorator,
items: sugars.map((sugar) {
return DropdownMenuItem(
value: sugar,
child: Text('$sugar sugars'),
);
}).toList(),
onChanged: (val) => setState(() => _currentSugars = val ),
),
SizedBox(height: 10.0),
Slider(
value: _currentStrength.toDouble() ?? userData.strength,
activeColor: Colors.brown[_currentStrength ?? userData.strength],
inactiveColor: Colors.brown[_currentStrength ?? userData.strength],
min: 100.0,
max: 900.0,
divisions: 8,
onChanged: (val) => setState(() => _currentStrength = val.round()),
),
RaisedButton(
color: Colors.pink[400],
child: Text(
'Update',
style: TextStyle(color: Colors.white),
),
onPressed: () async {
print(_currentName);
print(_currentSugars);
print(_currentStrength);
}
),
],
),
);
} else {
print('snapshot doesnt have data');
return Loading();
}
}
);
This is the stream
Stream<UserData> get userData {
return brewCollection.document(uid).snapshots().map(_userDataFromSnapshot);
}
brewCollection-
final CollectionReference brewCollection = Firestore.instance.collection('brews');
_userDataFromSnapshot
UserData _userDataFromSnapshot(DocumentSnapshot snapshot) {
return UserData(
uid: uid,
name: snapshot.data['name'],
sugars: snapshot.data['sugars'],
strength: snapshot.data['strength']
);
}
UserData
class UserData {
final String uid;
final String name;
final String strength;
final int sugars;
UserData({ this.uid, this.sugars, this.strength, this.name });
}
Since I am new to Flutter I don't know how to approach this issue.
Please help me out. Thank you.

Normally you need to check 3 cases:
snapshot is null (before any stream or action is fired, usually right
when the Widget is created)
snapshot is not null and data is null/invalid (stream is registered but no data is emitted yet)
snapshot is not null and data is valid (received data)

The UserData class should have been
class UserData {
final String uid;
final String name;
final int strength; // type int not String
final String sugars; // type String not int
UserData({ this.uid, this.sugars, this.strength, this.name });
}
I got confused with datatypes of strength and sugars. My bad.
It was a stupid mistake.
Thank you for responding :)

Here you have not handle error so it will be difficult to know what happens by just seeing code so also catch error on snapshot as
if(snapshot.hasData){
//Show widget with data
}else if(snapshot.hasError){
return Center(
child:Text(snapshot.error.toString());
);
}else{
return CircularProgressIndicator();
}

Related

Flutter Dropdown value not updating and UI not updating when submitting

I am working on a flutter project to display a Scrumboard, this is my first time working with flutter. I have a TextButton that opens a dialog when onpressed. The dialog is a form where you can edit details about a Scrumtask. I have discovered 2 issues. 1. issue is when i use the dropdownmenu and select a value. The value doenst get updated. 2. Lets say i edit a Scrumtask and set the state from 'Todo' to 'In Progress' from the dropdownmenu and submit, the UI doesn't move that Task object to the 'In Progress' column.
I hope the code I have provided is enough otherwise let me know.
Scrumboardscreen.dart
TextButton(
child: Text('Change'),
onPressed: () {
showDialog(
context: context,
builder: ((context) {
return AlertDialog(
title: Text('Task Informations'),
content: Form(
key: _formKey,
child: Column(
children: [
Text('Task Name'),
TextFormField(
controller: textscontrollers[0],
decoration: InputDecoration(
hintText: data[listIndex]
.items[itemIndex]
.taskName,
),
validator: (text) {
if (text == null || text.isEmpty) {
return 'Task Name cant be empty';
}
return null;
},
),
Text('Task Description'),
TextFormField(
controller: textscontrollers[1],
decoration: InputDecoration(
hintText: data[listIndex]
.items[itemIndex]
.taskDescription,
),
validator: (text) {
if (text == null || text.isEmpty) {
return 'Task Description cant be empty';
}
return null;
},
),
Text('Story Points'),
TextFormField(
controller: textscontrollers[2],
decoration: InputDecoration(
hintText: data[listIndex]
.items[itemIndex]
.storyPoints
.toString(),
),
validator: (text) {
if (text == null || text.isEmpty) {
return 'Story Points cant be empty';
}
return null;
},
),
Text('Task State'),
DropdownButton<String>(
value: data[listIndex]
.items[itemIndex]
.taskState,
icon: const Icon(Icons.arrow_downward),
elevation: 16,
style: const TextStyle(
color: Colors.blue,
),
underline: Container(
height: 2,
color: Colors.blue,
),
onChanged: (String? value) {
setState(() {
data[listIndex]
.items[itemIndex]
.taskState = value!;
});
},
items: states
.map<DropdownMenuItem<String>>(
(String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!
.validate()) {
_UpdateTask(data[listIndex]
.items[itemIndex]);
ScaffoldMessenger.of(context)
.showSnackBar(const SnackBar(
content:
Text('Updated Task')));
Navigator.of(context).pop();
}
},
child: Text('Submit Changes'),
)
],
),
),
);
}));
},
),
This method is being in the Futurebuilder and shows data on the UI
Future<List<BoardPostColumn>> GetData() async {
if (data.isEmpty) {
data = await manager.GetData();
}
return data;
}
The update method
void _UpdateTask(BoardPost task) async {
task.taskName = textscontrollers[0].text;
task.taskDescription = textscontrollers[1].text;
task.storyPoints = int.parse(textscontrollers[2].text);
BoardPost result = await manager.UpdateTask(task);
setState(() {
//My understanding that this would update the UI
task = result;
textscontrollers[0].clear();
textscontrollers[1].clear();
textscontrollers[2].clear();
});
}
Manager class
Future<BoardPost> UpdateTask(BoardPost task) async {
return handler.UpdateTask(task);
}
ApiHandler.dart
Future<BoardPost> UpdateTask(BoardPost task) async {
Response response = await post(
Uri.parse('https://localhost:7252/api/ScrumTask/UpdateScrumTask'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, dynamic>{
'id': task.id,
'taskName': task.taskName,
'taskDescription': task.taskDescription,
'storyPoints': task.storyPoints,
'taskState': task.taskState
}));
if (response.statusCode == 200) {
Map<String, dynamic> ScrumMap = jsonDecode(response.body);
return BoardPost.fromJson(ScrumMap);
} else {
throw Exception('Failed to Update');
}
}
class BoardPost {
int? id;
String? taskName;
String? taskDescription;
int? storyPoints;
String? taskState;
BoardPost(
{this.id,
this.taskName,
this.taskDescription,
this.storyPoints,
this.taskState});
BoardPost.fromJson(Map<String, dynamic> json) {
id = json['id'];
taskName = json['taskName'];
taskDescription = json['taskDescription'];
storyPoints = json['storyPoints'];
taskState = json['taskState'];
}
This class here is the one that will be displaying all the data
class BoardPostColumn {
String title;
List<BoardPost> items;
BoardPostColumn({
required this.title,
required this.items,
});
}
Manager class that returns the data provided from api
Future<List<BoardPostColumn>> GetData() async {
Response responseBody = await handler.GetData();
List<BoardPostColumn> data = [];
List<BoardPost> tasks = (json.decode(responseBody.body) as List)
.map((data) => BoardPost.fromJson(data))
.toList();
data.add(BoardPostColumn(
title: 'To do',
items:
tasks.where((e) => e.taskState?.toLowerCase() == 'todo').toList()));
data.add(BoardPostColumn(
title: 'In Progress',
items: tasks
.where((e) => e.taskState?.toLowerCase() == 'in progress')
.toList()));
data.add(BoardPostColumn(
title: 'Done',
items:
tasks.where((e) => e.taskState?.toLowerCase() == 'done').toList()));
return data;
}
Try to use StatefulBuilder inside showDialog to update the dialog ui.
showDialog(
context: context,
builder: (context) {
return StatefulBuilder(builder: (context, setStateSB) => AlertDialog(...),)
},
);
Use this setStateSB as setState to update the dialog ui, You may need to pass this setStateSB on the function or call it setStateSB((){}) at the end of the function call.

Flutter how to check if textfield is not empty on submit using rxdart

I have 2 text fields that checks if empty On value change but the problem is if I press the submit function and the text fields are empty it does not show the error. How do I implement checking values on submit and show the error on text field?
UI
Widget buildColumn() => Form(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: buildTitle(),
),
SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: buildDesription(),
),
buildSubmit()
],
));
Widget buildTitle() => StreamBuilder<String>(
stream: manager.title,
builder: (context, snapshot) {
debugPrint(snapshot.toString());
return TextField(
onChanged: manager.inTitle.add,
decoration: InputDecoration(
labelText: 'Title',
labelStyle: TextStyle(fontWeight: FontWeight.bold),
errorText:
snapshot.error == null ? null : snapshot.error.toString()),
);
});
Widget buildDesription() => StreamBuilder<String>(
stream: manager.description,
builder: (context, snapshot) {
return TextField(
onChanged: manager.inDescription.add,
decoration: InputDecoration(
labelText: 'Description',
labelStyle: TextStyle(fontWeight: FontWeight.bold),
errorText:
snapshot.error == null ? null : snapshot.error.toString()),
);
});
Widget buildSubmit() => StreamBuilder<Object>(
stream: manager.isFormValid,
builder: (context, snapshot) {
return ElevatedButton(
onPressed: () {
if (snapshot.hasData) {
manager.submit();
debugPrint("YEs");
}
},
child: Text("SEND"));
});
}
Manager
On submit does not check if title and description is empty
class CreatePostManager with Validation {
final _repository = PostRepository();
final _postsFetcher = PublishSubject<Post>();
Stream<Post> get addPost => _postsFetcher.stream;
final _title = BehaviorSubject<String>();
Stream<String> get title => _title.stream.transform(validateTitle);
Sink<String> get inTitle => _title.sink;
final _description = BehaviorSubject<String>();
Stream<String> get description =>
_description.stream.transform(validateDescription);
Sink<String> get inDescription => _description.sink;
final _loading = BehaviorSubject<bool>();
Stream<bool> get loading => _loading.stream.transform(checkLoading);
Sink<bool> get inLoading => _loading.sink;
Stream<bool> get isFormValid =>
Rx.combineLatest2(title, description, (a, b) => true);
Future submit() async {
inLoading.add(true);
String title = _title.value;
String description = _description.value;
Post itemModel = await _repository.addPost(title, description);
_postsFetcher.sink.add(itemModel);
inLoading.add(false);
return itemModel;
}
dispose() {
_postsFetcher.close();
}
}
Validator
mixin Validation {
final validateTitle =
StreamTransformer<String, String>.fromHandlers(handleData: (value, sink) {
if (value.isEmpty) {
sink.addError("Title cannot be empty");
}else {
sink.add(value);
}
});
final validateDescription =
StreamTransformer<String, String>.fromHandlers(handleData: (value, sink) {
if (value.isEmpty) {
sink.addError("Description cannot be empty");
}else {
sink.add(value);
}
});
final checkLoading =
StreamTransformer<bool, bool>.fromHandlers(handleData: (value, sink) {
sink.add(value);
});
}
On submit I want to check if textfields are empty and show the Error message. Currently it only shows when user types.
On submission, the TextField can be validated using its Controllers.
_textFieldController = TextEditingController();
...
TextField(
controller: _textFieldController,
)
The TextField value can then be fetched using TextEditingController().value.text. Add a checker against this value on submission.
Another way of validating TextFields is with the use of the validator in TextFormField
TextFormField(
// When a String is returned, it's displayed as an error on the TextFormField
validator: (String? value) {
return (value == null || value == '') ? 'Field should not be empty.' : null;
},
)

how to get data in material datatable in flutter from rest api

I want to show data in material dataTable but instead of getting data in a row it makes the table head every time with the row
My design which I Want:
The design I am getting right now:
Here is the full code of my flutter application:
UI SCREEN
________________This is Ui part of my application_
import 'package:aiims/bloc/add_relatives_bloc.dart';
import 'package:aiims/models/relative.dart';
import 'package:aiims/service/api.dart';
import 'package:aiims/widgets/side_menu_widget.dart';
import 'package:flutter/material.dart';
import 'package:outline_material_icons/outline_material_icons.dart';
import 'package:provider/provider.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
class RelativeScreen extends StatefulWidget {
#override
_RelativeScreenState createState() => _RelativeScreenState();
}
class _RelativeScreenState extends State<RelativeScreen> {
GlobalKey<FormState> _formKey = GlobalKey<FormState>();
DataRow getDataRow(data) {
return DataRow(
cells: <DataCell>[
DataCell(Text(data["name"])),
DataCell(Text(data["age"])),
DataCell(Text(data["relation"])),
DataCell(
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.edit_outlined),
Icon(Icons.delete_outline_sharp),
],
),
),
],
);
}
#override
void initState() {
// TODO: implement initState
super.initState();
// _getDataRow(list.length);
}
// _getRelatives() {
// getRelativesList().then((Relative) {
// setState(() {
//
// });
// print("Length: ${_relative.length}");
// });
// }
// DataRow _getDataRow(list) {
// return DataRow(
// cells: <DataCell>[
// DataCell(Text(list["name"])),
// DataCell(Text(list["age"])),
// DataCell(Text(list["relation"])),
// ],
// );
// }
#override
Widget build(BuildContext context) {
final bloc = Provider.of<AddNewRelativeBloc>(context, listen: false);
return Scaffold(
drawer: NavDrawer(),
appBar: AppBar(
title: Text('Relatives'),
actions: <Widget>[
GestureDetector(
onTap: () => _add_relavitves(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Icon(Icons.add_circle_outline_rounded),
),
),
],
),
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Container(
// alignment: Alignment.center,
padding: EdgeInsets.symmetric(vertical: 20),
child: SingleChildScrollView(
child: Container(
child: FutureBuilder(
future: getRelativesList(),
// initialData: new TreatmentDetail(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text(snapshot.error.toString()),
);
} else if (snapshot.connectionState ==
ConnectionState.done) {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) {
return DataTable(
headingRowColor: MaterialStateColor
.resolveWith(
(states) => Color(0xffff69b4)),
// MaterialStateColor.resolveWith((states) => Colors.pink),
columns: [
DataColumn(label: Text("Name")),
DataColumn(label: Text("Age")),
DataColumn(label: Text("Relation")),
DataColumn(label: Text("Action")),
],
// rows: List.generate(
// list.length, (index) =>
// _getDataRow(list[index]),
// ));
rows: [
DataRow(
cells: [
DataCell(Text(list[index]["name"])),
DataCell(Text(list[index]["age"])),
DataCell(Text(list[index]["relation"])),
DataCell(
Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Icon(Icons.edit_outlined),
Icon(Icons.delete_outline_sharp),
],
),
),
],
),
// DataRow(cells: [
// DataCell(Text('Ajay Singh')),
// DataCell(Text('25')),
// DataCell(Text('Son')),
// DataCell(
// Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Icon(Icons.edit_outlined),
// Icon(Icons.delete_outline_sharp),
// ],
// ),
// ),
// ]),
],
);
});
} else {
return Center(
child: CircularProgressIndicator(),
);
}
}),
),
),
),
),
),
);
}
void _add_relavitves() {
final bloc = Provider.of<AddNewRelativeBloc>(context, listen: false);
// set up the buttons
// Widget cancelButton = FlatButton(
// child: Text("Cancel"),
// onPressed: () {
// Navigator.pop(context);
// },
// );
Widget cancelButton = MaterialButton(
child: Container(
height: 40,
width: 110,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Color(0xffff69b4),
),
child: Text(
"Discard",
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
),
onPressed: () {
Navigator.pop(context);
},
);
Widget continueButton = FlatButton(
child: Text("Continue"),
onPressed: () {},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Center(
child: Text(
"Add New Relative",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
content: SingleChildScrollView(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(height: 20),
StreamBuilder<Object>(
stream: bloc.name,
builder: (context, snapshot) {
return TextField(
keyboardType: TextInputType.text,
decoration: InputDecoration(
hintText: "Name",
labelText: "Name",
errorText: snapshot.error,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
// onChanged: (value) => bloc.changeName,
onChanged: bloc.changeName,
);
}),
SizedBox(height: 20),
StreamBuilder<Object>(
stream: bloc.age,
builder: (context, snapshot) {
return TextField(
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: "Age",
labelText: "Age",
errorText: snapshot.error,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
// onChanged: (value) => bloc.changeName,
onChanged: bloc.changeAge,
);
}),
SizedBox(height: 20),
StreamBuilder<Object>(
stream: bloc.relation,
builder: (context, snapshot) {
return TextField(
keyboardType: TextInputType.text,
decoration: InputDecoration(
hintText: "Relation",
labelText: "Relation",
errorText: snapshot.error,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
// onChanged: (value) => bloc.changeName,
onChanged: bloc.changeRelation,
);
}),
SizedBox(height: 20),
StreamBuilder<Object>(
stream: bloc.phoneNumber,
builder: (context, snapshot) {
return TextField(
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: "Phone Number",
labelText: "Phone Number",
errorText: snapshot.error,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
),
// onChanged: (value) => bloc.changeName,
onChanged: bloc.changePhoneNumber,
);
}),
],
),
),
),
actions: [
cancelButton,
_saveButton(),
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
Widget _saveButton() {
final bloc = Provider.of<AddNewRelativeBloc>(context, listen: false);
return StreamBuilder<Object>(
stream: bloc.isValid,
builder: (context, snapshot) {
return GestureDetector(
onTap: snapshot.hasError || !snapshot.hasData
? null
: () {
bloc.submit();
},
child: Container(
height: 40,
width: 130,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: snapshot.hasError || !snapshot.hasData
? Color(0xffff69b4)
: Colors.green,
),
child: Text(
"Save",
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
),
);
});
}
}
//My Future for getting response from api
Future<List> getRelativesList() async {
final response =
await http.get("$baseUrl");
Map<String, dynamic> map = json.decode(response.body);
List<dynamic> data = map["data"];
print("first id on console" + data[0]["id"].toString());
return list = data;
}
//Here is my model
class Relative {
int _code;
String _message;
List<Data> _data;
Relative({int code, String message, List<Data> data}) {
this._code = code;
this._message = message;
this._data = data;
}
int get code => _code;
set code(int code) => _code = code;
String get message => _message;
set message(String message) => _message = message;
List<Data> get data => _data;
set data(List<Data> data) => _data = data;
Relative.fromJson(Map<String, dynamic> json) {
_code = json['code'];
_message = json['message'];
if (json['data'] != null) {
_data = new List<Data>();
json['data'].forEach((v) {
_data.add(new Data.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['code'] = this._code;
data['message'] = this._message;
if (this._data != null) {
data['data'] = this._data.map((v) => v.toJson()).toList();
}
return data;
}
}
class Data {
int _id;
int _patientId;
String _name;
String _age;
String _relation;
String _phoneNo;
Data(
{int id,
int patientId,
String name,
String age,
String relation,
String phoneNo}) {
this._id = id;
this._patientId = patientId;
this._name = name;
this._age = age;
this._relation = relation;
this._phoneNo = phoneNo;
}
enter code here
int get id => _id;
set id(int id) => _id = id;
int get patientId => _patientId;
set patientId(int patientId) => _patientId = patientId;
String get name => _name;
set name(String name) => _name = name;
String get age => _age;
set age(String age) => _age = age;
String get relation => _relation;
set relation(String relation) => _relation = relation;
String get phoneNo => _phoneNo;
set phoneNo(String phoneNo) => _phoneNo = phoneNo;
Data.fromJson(Map<String, dynamic> json) {
_id = json['id'];
_patientId = json['patient_id'];
_name = json['name'];
_age = json['age'];
_relation = json['relation'];
_phoneNo = json['phone_no'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this._id;
data['patient_id'] = this._patientId;
data['name'] = this._name;
data['age'] = this._age;
data['relation'] = this._relation;
data['phone_no'] = this._phone`enter code here`No;
return data;
}
}
You return DataTable as an item for ListView.builder, that is why you see header and row. If you want display only table then remove ListView widget and return DataTable with rows. Something like
FutureBuilder(
builder: (context, snapshot) {
return DataTable(
columns: <DataColumn>[
...
],
rows: list.map((item) {
return DataRow(
cells: <DataCell>[...]
);
}),
),
}
)

converting documentSnapshot to a custom model with flutter and firestore

I have the code which only deals with searching words from cloud_firestore which is a firebase service. Searching is done fine and everything is working fine but i would love to convert my firebase DocumentSnapshot to a custom model. I don't wan;t it to be showing instace of 'DocumentSnapShot' of which i don't wan't. I wan't it to be showing at least instance of 'WordsSearch' when i print(data):
Full search code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:kopala_dictionary/models/words.dart';
import 'package:kopala_dictionary/screens/author/word_details.dart';
import 'package:provider/provider.dart';
class CloudFirestoreSearch extends StatefulWidget {
#override
_CloudFirestoreSearchState createState() => _CloudFirestoreSearchState();
}
class _CloudFirestoreSearchState extends State<CloudFirestoreSearch> {
String name = "";
//words list from snappshots
List<Words> _wordsFromSnapShots(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return Words(
word: doc.data['word'],
englishTranslation: doc.data['english_translation'],
bembaTranslation: doc.data['bemba_translation'],
);
}).toList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop();
},
),
title: Card(
child: TextField(
decoration: InputDecoration(
prefixIcon: Icon(Icons.search), hintText: 'Search...'),
onChanged: (val) {
setState(() {
name = val;
});
},
),
),
),
body: StreamBuilder<QuerySnapshot>(
stream: (name != "" && name != null)
? Firestore.instance
.collection('words')
.where("word", isGreaterThanOrEqualTo: name)
.snapshots()
: Firestore.instance.collection("words").snapshots(),
builder: (context, snapshot) {
return (snapshot.connectionState == ConnectionState.waiting)
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot data = snapshot.data.documents[index];
print(data);
return Card(
child: Column(
children: <Widget>[
SizedBox(
width: 25,
),
ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
WordsDetails(word: data)),
);
},
leading: Text(
data['word'],
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 20,
),
),
),
],
),
);
},
);
},
),
);
}
}
And this is a custom WordsSearch class:
class WordsSearch {
final String word;
final String englishTranslation;
final String bembaTranslation;
final DateTime date_posted;
Words(
{this.word,
this.englishTranslation,
this.bembaTranslation,
this.date_posted});
}
You can do this using a .fromFirestore method in your model object.
factory Words.fromFirestore(DocumentSnapshot doc) {
Map data = doc.data();
// you likely need to convert the date_posted field. something like this
Timestamp datePostedStamp = data['date_posted'];
var datePosted = new DateTime.fromMillisecondsSinceEpoch(datePostedStamp.millisecondsSinceEpoch);
return Words(
word: data['word'] ?? '',
englishTranslation: data[''] ?? '',
bembaTranslation: data['bembaTranslation'] ?? '',
date_posted: datePosted
);
}
Elsewhere...
List<Words> _wordsFromSnapShots(QuerySnapshot snapshot) {
return snapshot.documents.map((doc) {
return Words.fromFirestore(doc);
}).toList();
}
Remember to convert the date posted before sending into firebase. Here's how I've done it.
Timestamp.fromMillisecondsSinceEpoch(DateTime.now().millisecondsSinceEpoch)

Flutter DropdownButton NoSuchMethodError: The getter value was called on null

I'm trying to populate a dropdown button in my flutter app with data coming from my restful api. However i get the error above.
Here is my model;
class SavingsCategory extends Equatable{
final int id;
final String name;
SavingsCategory({
#required this.id,
#required this.name
});
#override
List<Object> get props => [name, id];
}
My repository fetching the data
#override
Future<List<SavingsCategory>> getSavingsCategory()
async {
var token = await tokenRepository.getToken();
final response = await http.get(
'$baseUrl/user/savings-category',
headers: {HttpHeaders.authorizationHeader: 'Bearer $token'},
);
if (response.statusCode == 200) {
var data = json.decode(response.body);
List<SavingsCategory> categoryList = data['savingsCategory'].map<SavingsCategory>((json) {
return SavingsCategory.fromJson(json);
}).toList();
return categoryList;
} else {
throw new Exception("Couldn't get any saving categories");
}
}
My bloc code
class SavingsCategoryBloc {
final repository = SavingsRepository();
final _savingsCategories = PublishSubject<List<SavingsCategory>>();
Stream<List<SavingsCategory>> get savingsCategories => _savingsCategories.stream;
fetchSavingsCategories() async {
final categories = await repository.getSavingsCategory();
_savingsCategories.sink.add(categories);
}
dispose(){
_savingsCategories.close();
}
}
Finally my widget
class _StartSavingPageState extends State<StartSavingPage> {
final SavingsCategoryBloc bloc = SavingsCategoryBloc();
#override
void initState() {
bloc.fetchSavingsCategories();
super.initState();
}
#override
Widget build(BuildContext context) {
....
Container(
padding: EdgeInsets.symmetric(
horizontal: 15.0, vertical: 10.0),
child: StreamBuilder<List<SavingsCategory>>(
stream: bloc.savingsCategories,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return DropdownButton<String>(
items: [
DropdownMenuItem<String>(
child: Text('No Savings Category'),
value: '',
),
],
onChanged: (String value) {
setState(() {
});
},
isExpanded: true,
hint: Text(
'SAVING FOR?',
style: TextStyle(
fontSize: 15.0, color: Colors.grey),
),
);
}
return DropdownButton(
value: category,
items: snapshot.data.map((category) {
DropdownMenuItem(
child: Text('${category.name}'),
value: category.id,
);
}).toList(),
onChanged: (value) {
setState(() {
category = value;
});
},
isExpanded: true,
hint: Text(
'SAVING FOR?',
style: TextStyle(
fontSize: 15.0, color: Colors.grey),
),
);
}),
),
}
}
How can i fix this error? I know the data fetching works just fine. I'm definitely missing something in my widget. Any help would be appreciated.
The DropdownButton value must in item values or must be null.
DropdownButton(
value: categoryId,
items: snapshot.data.map((category) {
DropdownMenuItem(
child: Text('${category.name}'),
value: category.id,
);
}).toList(),
onChanged: (value) {
setState(() {
categoryId = value;
});
},
isExpanded: true,
hint: Text(
'SAVING FOR?',
style: TextStyle(
fontSize: 15.0, color: Colors.grey),
),
);
The mistake you've made is not returning the DropdownMenuItem from the map.
So:
snapshot.data.map((category) {
DropdownMenuItem(
child: Text('${category.name}'),
value: category.id,
);
})
should instead be:
snapshot.data.map((category) =>
DropdownMenuItem(
child: Text('${category.name}'),
value: category.id,
);
)