Flutter Firebase Iterate through all documents that matches field value and inserting field from each one into a List of String - flutter

I'm trying to get afield value into a list of string from documents in the firebase that match the WHERE of a field value from a value of my own.
(I wanna show images above each other to create a full picture depending on the data, using two fields to be exact and then creating a list of images to show as positioned inside a Stack)
my code:
Implant Class:
class Implant {
final String id;
final String pid;
final String aptid;
late String type;
late double? sizew;
late int? sizel;
late String? positionQ;
late int? positionN;
Implant(this.id, this.pid, this.aptid, this.type, this.sizew, this.sizel,
this.positionQ, this.positionN);
Map<String, dynamic> toJson() => {
'ID': id,
'PID': pid,
'APTID': aptid,
'TYPE': type,
'SIZEW': sizew,
'SIZEL': sizel,
'POSITIONQ': positionQ,
'POSITIONN': positionN,
};
factory Implant.fromJson(Map<String, dynamic> json) {
return Implant(
json['ID'],
json['PID'],
json['APTID'],
json['TYPE'],
json['SIZEW'],
json['SIZEL'],
json['POSITIONQ'],
json['POSITIONN'],
);
}
static Map<String, dynamic> toMap(Implant implant) => {
'ID': implant.id,
'PID': implant.pid,
'APTID': implant.aptid,
'TYPE': implant.type,
'SIZEW': implant.sizew,
'SIZEL': implant.sizel,
'POSITIONQ': implant.positionQ,
'POSITIONN': implant.positionN,
};
static String encode(List<Implant> implant) => json.encode(
implant
.map<Map<String, dynamic>>((implant) => Implant.toMap(implant))
.toList(),
);
static List<Implant> decode(String implants) =>
(json.decode(implants) as List<dynamic>)
.map<Implant>((implant) => Implant.fromJson(implant))
.toList();
}
Future of list of string function:
static Future<List<String>> getImplants(Patient patient) async {
List<String> result = ['assets/images/teeth/empty.png'];
var collection = FirebaseFirestore.instance.collection('implants');
var docSnapshot = await collection.doc(patient.id).get();
if (docSnapshot.exists) {
Map<String, dynamic> data = docSnapshot.data()!;
result.add(data['POSITIONQ'] + data['POSITIONN']);
}
return result;
}
How I translate them into Stack and Positioned:
static Future<void> showPatientImplants(BuildContext context, Patient patient,
{bool spaces = true}) async {
List<String> myImplants;
myImplants = await getImplants(patient);
List<Widget> x = [Image.asset('assets/images/teeth/empty.png')];
myImplants.forEach((element) {
x.add(Image.asset(element));
});
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
scrollable: true,
content: SingleChildScrollView(
child: GestureDetector(
onTap: () {
log(myImplants.toString());
},
child: Stack(
children: x,
),
),
),
);
});
}
The only problem I'm facing -I think- is that the Future<List> function doesn't get me the values I need.
I tried using other functions I found on SO and google, nothing worked.
I guess I can try using the stream and Listview.builder just to get the files names but it seems like exactly how it shouldn't be done.
any help is appreciated.

Never mind Solved it using the following code:
static Future<List<String>> getImplants(Patient patient) async {
List<String> result = ['assets/images/teeth/empty.png'];
log('ID:${patient.id}');
var collection = FirebaseFirestore.instance
.collection('implants')
.where('PID', isEqualTo: patient.id);
var docSnapshot = await collection.get();
for (var element in docSnapshot.docs) {
if (element.exists) {
Map<String, dynamic> data = element.data();
result.add(data['POSITIONQ'].toString() + data['POSITIONN'].toString());
}
}
return result;
}
but if anyone has a better idea I appreciate it along with everyone who has a similar problem.

Related

How do I Display Nested json array in flutter?

Whenever I try accessing a field from the nested array I get the following Range error. Not sure what's gone wrong, any help would be appreciated.
RangeError (index): Invalid value: Only valid value is 0: 1
This is my User Model :
import 'dart:convert';
List<User> userFromJson(String str) =>
List<User>.from(json.decode(str).map((x) => User.fromJson(x)));
class User {
User({
required this.userName,
required this.facilities,
});
final String userName;
final List<Facility>? facilities;
factory User.fromJson(Map<String, dynamic> json) => User(
userName: json["userName"],
facilities: json["facilities"] == null
? null
: List<Facility>.from(
json["facilities"].map((x) => Facility.fromJson(x))),
);
}
class Facility {
Facility({
required this.id,
required this.employeeGlobalId,
required this.facilityId,
});
final int id;
final int employeeGlobalId;
final int facilityId;
factory Facility.fromJson(Map<String, dynamic> json) => Facility(
id: json["id"],
employeeGlobalId: json["employeeGlobalId"],
facilityId: json["facilityId"],
);
}
This is how I connect with the API and convert the response to a list.
class ApiService {
final url = Uri.parse("http://47.254.237.237:81/api/Users/GetAllUser");
Map<String, String> headers = {
"Accept": 'application/json',
'content-type': 'application/json',
'Authorization':
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYyIiwibmJmIjoxNjY2OTU0NTEzLCJleHAiOjE2NjcwNDA5MTMsImlhdCI6MTY2Njk1NDUxM30.EXEzTX8-MHQqZuuILwHGoQ0Vpw2fAgsi2QypGNFgMAE',
'userId': '62'
};
Future<List<User>> fetchUsers(http.Client client) async {
final response = await client.get(url, headers: headers);
return compute(parseUsers, response.body);
}
List<User> parseUsers(String response) {
final parsed = jsonDecode(response).cast<Map<String, dynamic>>();
return parsed.map<User>((json) => User.fromJson(json)).toList();
}
}
Lastly, This is how I am trying to display the facility ID.
ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text(users[index].userName),
subtitle: Text(users[index].facilities![index].facilityId.toString()),
);
},
);
Probably facilities![index] is causing the issue. You're trying to access facilities values at index range, builder's current position, while only one value might be available.
Change to facilities![0]. Or use loop/map fn to print each facilities if there are more than 1 facilities values.
Updated code:
ListView.builder(
itemBuilder: (context, index) {
return ListTile(
title: Text(users[index].userName),
subtitle: Text(users[index].facilities![0].facilityId.toString()),
);
},
);

converting a nested map to json for patching

I'm a newbie to dart and flutter: I would like to patch a map with a nested list in it, but I can't get it done. The code below returns a 400-error code.
Future<void> updateExerciseHistory(List<Exercise> exerciseList) async {
exerciseList.forEach((e) async {
try {
final url = Uri.https(urlBase, '/exercises/${e.id}.json');
await http.patch(
url,
body: json.encode({
'history': e.history.map((key, value) => MapEntry(
key.toIso8601String(),
value
.map((s) => {
'id': s.id,
'weight': s.weight,
'reps': s.reps,
'rir': s.rir,
'notes': s.notes,
})
.toList())),
}),
);
_userExercises
.firstWhere((ue) => ue.id == e.id)
.history
.addAll(e.history);
notifyListeners();
} catch (error) {
throw error;
}
}); }
class Exercise with ChangeNotifier {
String id;
String name;
Map<Muscle, double> musclesInvolved;
Map<DateTime, List<Set>>
history;
Exercise({
this.id,
this.name,
this.musclesInvolved,
this.history,
});
}
class Muscle {
final String id;
String name;
Muscle({this.id, this.name});
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
};
}
It is probably due to the return of a MapEntry. When I call the .map on a list I can return a map, but I can't do that when I call .map on a Map (can only return a MapEntry or am I missing something)?
Does somebody have a solution for this? :-D
Thanks for any help!

Flutter: Parsing JSON data and showing in App

I am very new to Flutter and Dart.
I have a signup page and I would like to show error in the App. My backend page is returning the errors and status in JSON format. Like below.
{"errors":{"Password1":"Password could not be empty",
"Email1":"Invalid Email Format",
"Name":"Your name must be between 3 to 30 characters!"},
"success":false}
I created a file for JSON parsing like below.
import 'dart:convert';
Signup signupFromJson(String str) => Signup.fromJson(json.decode(str));
String signupToJson(Signup data) => json.encode(data.toJson());
class Signup {
Errors errors;
bool success;
Signup({
this.errors,
this.success,
});
factory Signup.fromJson(Map<String, dynamic> json) => Signup(
errors: Errors.fromJson(json["errors"]),
success: json["success"],
);
Map<String, dynamic> toJson() => {
"errors": errors.toJson(),
"success": success,
};
}
class Errors {
String password1;
String email1;
String name;
Errors({
this.password1,
this.email1,
this.name,
});
factory Errors.fromJson(Map<String, dynamic> json) => Errors(
password1: json["Password1"],
email1: json["Email1"],
name: json["Name"],
);
Map<String, dynamic> toJson() => {
"Password1": password1,
"Email1": email1,
"Name": name,
};
}
Now I need to show this data to App after Async call.
Future userRegistration() async{
try{
// Showing CircularProgressIndicator.
setState(() {
visible = true ;
});
// Getting value from Controller
String name = nameController.text;
String email = emailController.text;
String password = passwordController.text;
// SERVER API URL
var url = 'http://192.168.100.10:8080/app/registerexec.php';
// Store all data with Param Name.
var data = {'name': name, 'email': email, 'password' : password};
// Starting Web API Call.
var response = await http.post(url, body: json.encode(data));
// Getting Server response into a variable.
final message = signupFromJson(response.body);
if(response.statusCode == 200){
setState(() {
visible = false;
});
}
// Showing Alert with Response JSON Message.
}catch(e){
return userRegistration();
}
}
How can I show the JSON data to SnackBar?
Edit
I managed to get the data in Print after manually defining it. Like below. But I want to automate it. So, if there are any errors it can show and if its successful then a different message.
print(message.errors.email1);
print(message.errors.name);
print(message.errors.password1);
print(message.success);
you could use FutureBuilder at your snackBar. I've edited from the code available here:
class SnackBarPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: userRegistration,
initialData: '',
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
// snapshot.data = yourData from your userRegistration
// print(snapshot.data) to show your data here
return snackBar = SnackBar(
content: Text('Yay! A SnackBar!'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
},
)
};
)
},
),
}
}

Flutter Firestore array with Maps. Do they work?

I can't stream (read) Documents that contain array properties that are Maps in Firestore.
Using Firestore with a document containing an array with a simple String type works as expected. Easy to write (append with FieldValue.arrayUnion(['data1','data2','data3']) and stream back out with code like:
var test2 = List<String>();
for (var item in data['items']) {
print('The item $item');
test2.add(item);
}
test2 can now be used as my items property.
When I try and use a List where item type becomes a Map in Firestore and is just a simple Class containing a few Strings and a date property. I can write these to FireStore but I can't read them back out.
The following code fails: (no errors in the Debug Console but it doesn't run)
var test2 = List<Item>();
data['items'].forEach((item) {
var description = item['description'];
print('The description $description'); // <-- I don't get past this
final x = Item.fromMap(item); // <-- so I can't even attempt this
return test2.add(x);
});
I never get to actually call my Item.fromMap constructor: here is another try:
// fails
final theItems = data['items'].map((x) {
return Item.fromMap(x); // <-- never get here
});
Although the DEBUG CONSOLE doesn't say there is any problem. If I inspect theItems variable (the debugger 'jumps' a couple of lines down to my return) after the failed iteration it looks like this:
MappedListIterable
_f:Closure
_source:List (1 item)
first:Unhandled exception:\ntype '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of type 'Map<String, dynamic>'\n#0 new Listing.fromMap.<anonymous closure> isEmpty:false
isNotEmpty:true
iterator:ListIterator
last:Unhandled exception:\ntype '_InternalLinkedHashMap<dynamic, dynamic>' is not a subtype of
So bad things have happened but I have no idea why!
Has anyone actually written and retrieved Firestore array properties that contain Maps?
Any help in how to proceed would be greatly appreciated!
More: screen shot of the document
Here is the code that reads (streams) the collection
Stream<List<T>> collectionStream<T>({
#required String path,
#required T builder(Map<String, dynamic> data, String documentID),
#required String userId,
Query queryBuilder(Query query),
int sort(T lhs, T rhs),
}) {
Query query = Firestore.instance.collection(path);
if (queryBuilder != null) {
query = queryBuilder(query);
}
final Stream<QuerySnapshot> snapshots = query.snapshots();
return snapshots.map((snapshot) {
//print('document: path $path ${snapshot.documents[0]?.documentID}');
final result = snapshot.documents
.map((snapshot) => builder(snapshot.data, snapshot.documentID))
.where((value) => value != null)
.toList();
if (sort != null) {
result.sort(sort);
}
print('returning from CollectionStream');
return result;
});
}
the .map is where the problem comes in. The builder function resolves to this:
builder: (data, documentId) {
return Listing.fromMap(data, documentId);
},
Which ends up here
factory Listing.fromMap(
Map<String, dynamic> data,
String documentId,
) {
if (data == null) {
return null;
}
final theTime = (data['createdAt'] as Timestamp).toDate();
// see above code where I fail at getting at the items property/field
Here is the Item Class:
class Item {
Item(this.description, this.imageURL, this.thumbnailURL,
{this.status = ItemStatus.active,
this.type = ListingType.free,
this.price = 0,
this.isUpdate = false,
this.createdAt});
final String description;
final String imageURL;
final String thumbnailURL;
final ItemStatus status;
final ListingType type;
final int price;
final bool isUpdate;
DateTime createdAt;
factory Item.fromMap(
Map<String, dynamic> data,
) {
if (data == null) {
return null;
}
final theTime = (data['createdAt'] as Timestamp).toDate();
return Item(
data['description'],
data['imageURL'],
data['thumbnailURL'],
status: _itemStatusFromString(data['status']),
type: data['type'] == 'free' ? ListingType.free : ListingType.flatRate,
createdAt: theTime,
);
}
Map<String, dynamic> toMap() {
// enums are for shit in Dart
final statusString = status.toString().split('.')[1];
final typeString = type.toString().split('.')[1];
return {
'description': description,
'imageURL': imageURL,
'thumbnailURL': thumbnailURL,
'itemStatus': statusString,
'price': price,
'listingType': typeString,
if (!isUpdate) 'createdAt': DateTime.now(),
if (isUpdate) 'updatedAt': DateTime.now(),
};
}
}
The above is never called to read (we crash) ... it is called to write the data.
This is a known issue with Firestore. Here is the starting thread Flutter issues I found
From reading through the issues it seems lots of folks use a serializer package which is where the issue surfaced. Its still active...
Here is my solution NOT using a serializer and just doing it 'by hand'.
I was able simplify the problem and generate an error that was Google-able. Below is a single page which just writes and reads a single Document. The Class A and B are as small as can be.
So the code writes a single document to Firestore the contains two properties. A name and items. The items being the List of Class B that Class A contains. Checkout the Firebase console. The reading just does that.
No StreamController just the Console. The problem is in the fromMap method where we have to convert the array of objects in Firesote to a List of class instances in Dart. This should not be this difficult and at a minimum it should be documented ....
the line
var theRealItems = data['items'].map((i) => B.fromMap(i));
will generate the error. And needs to be replaced with
var theItems = data['items'].map((i) {
var z = Map<String, dynamic>.from(i);
print(z['description']);
return B.fromMap(z);
}).toList();
var theRealItems = List<B>.from(theItems);
Why this is so difficult is still a mystery to me! Anyone improving this code: I'm all ears.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class A {
A(this.name, this.items);
final name;
final List<B> items;
factory A.fromMap(
Map<String, dynamic> data,
String documentId,
) {
if (data == null) {
return null;
}
// we crash here !!!
var theRealItems = data['items'].map((i) => B.fromMap(i));
// uncomment the 6 lines below
// var theItems = data['items'].map((i) {
// var z = Map<String, dynamic>.from(i);
// print(z['description']);
// return B.fromMap(z);
// }).toList();
// var theRealItems = List<B>.from(theItems);
return A(data['name'], theRealItems);
}
Map<String, dynamic> toMap() {
var theItems = items.map((i) => i.toMap()).toList();
return {'name': name, 'items': theItems};
}
}
class B {
B(this.description);
final description;
factory B.fromMap(
Map<String, dynamic> data,
) {
if (data == null) {
return null;
}
return B(data['description']);
}
Map<String, dynamic> toMap() {
return {
'description': description,
};
}
}
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () => _write(),
child: Text('Write the Doc'),
),
RaisedButton(
onPressed: () => _read(),
child: Text('Read the Doc ... check Debug Console!'),
),
],
),
),
);
}
_write() async {
try {
var b = B('Inside B!');
List<B> theList = List<B>();
theList.add(b);
var a = A('myName', theList);
await Firestore.instance
.collection('test')
.document('testDoc')
.setData(a.toMap());
print('returning from write!');
} catch (e) {
print('Error ${e.toString()}');
}
}
_read() async {
try {
var aFromFs = await Firestore.instance
.collection('test')
.document('testDoc')
.get();
var a = A.fromMap(aFromFs.data, aFromFs.documentID);
print('the A from FireBase $a with name ${a.name} first item ${a.items.first.description}');
print('returning from read!');
} catch (e) {
print('Oh no Error! ${e.toString()}');
}
}
}
In the below code Name refers to the name of your respective document and collection.
Let's say you want to get "imageURL" and "thumbnailUrl" for now and update the values without deleting or changing other fields inside the array.
String imageUrl ;
String thumbnailUrl;
DocumentReference _docReference = FirebaseFirestore.instance.collection(NAME).doc(NAME);
//refering to the specific document
Map<String, dynamic> neededData= allDocsFromTraining.data();
//getting all the keys and values inside the document
List<Map<String, dynamic>> _yourDocument=
(neededData["items"] as List<dynamic>)
.map((m) => Map<String, dynamic>.from(m))
.toList();
//only getting the list of items
for (int index = 0; index < _yourDocument.length; index++) {
Map<String, dynamic> _getMap = _yourDocument[index];
if (_getMap["price"] == 0)
//giving a condition to get the desired value and make changes
{
//setting the variables with the value from the database
setState(() {
imageUrl = _getMap["imageURL"];
thumbnailUrl = _getMap["thumbnailURL"]
});
///Use this if you want to update value of imageURL and thumbnailURL at that specific index of an array
_getMap['imageURL'] = "Your Updated URL";
_getMap['thumbnailURL'] = "Your Updated Url;
break;
} else {}
}
await _docReference.update({"items": _yourDocument});
//this adds the updated value to your database.

How to use json and serialization with firebase and bloc? Error: Converting object to an encodable object failed

this is my cloud firestore looks like:
Error Message: Unhandled Exception: Converting object to an encodable
object failed: Photography
used jsonSerialization for my database
import 'package:json_annotation/json_annotation.dart';
part 'Model.g.dart';
#JsonSerializable()
class Photography{
String couplePhoto;
String female;
String image_url;
String info;
String male;
AllImages all_images;
Photography();
factory Photography.fromJson(Map<String, dynamic> json) => _$PhotographyFromJson(json);
Map<String,dynamic> toJson() => _$PhotographyToJson(this);
}
#JsonSerializable()
class AllImages {
List<String> imageUrl = List<String>();
AllImages();
factory AllImages.fromJson(Map<String, dynamic> json) => _$AllImagesFromJson(json);
Map<String,dynamic> toJson() => _$AllImagesToJson(this);
}
By running flutter pub run build_runner build in the project root, I generated JSON serialization code for my Photography and AllImages whenever they are needed.
Model.g.dart
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'Model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Photography _$PhotographyFromJson(Map<String, dynamic> json) {
return Photography()
..couplePhoto = json['couplePhoto'] as String
..female = json['female'] as String
..image_url = json['image_url'] as String
..info = json['info'] as String
..male = json['male'] as String
..all_images = json['all_images'] == null
? null
: AllImages.fromJson(json['all_images'] as Map<String, dynamic>);
}
Map<String, dynamic> _$PhotographyToJson(Photography instance) =>
<String, dynamic>{
'couplePhoto': instance.couplePhoto,
'female': instance.female,
'image_url': instance.image_url,
'info': instance.info,
'male': instance.male,
'all_images': instance.all_images
};
AllImages _$AllImagesFromJson(Map<String, dynamic> json) {
return AllImages()
..imageUrl = (json['imageUrl'] as List)?.map((e) => e as String)?.toList();
}
Map<String, dynamic> _$AllImagesToJson(AllImages instance) =>
<String, dynamic>{'imageUrl': instance.imageUrl};
After that, I created the DB class,
How to use the model class?
class DB {
final db = Firestore.instance;
// Stream<QuerySnapshot> initStream() {
// return db.collection('photography').snapshots();
// }
getPhotography() async {
return db.collection('photography')
.document("0yUc5QBGHNNq6WK9CyyF")
.setData(jsonDecode(jsonEncode(Photography)));
}
}
DB db = DB();
my photography_bloc class
class PhotographyBloc extends BlocBase{
//PhotographyBloc(){
// db.initStream().listen((data) => inFirestore.add(data));
//}
PhotographyBloc(){
init();
}
Photography photography;
//final _firestoreController = StreamController<Photography>();
//Stream<Photography> get outFirestore => _firestoreController.stream;
//Sink<Photography> get inFirestore => _firestoreController.sink;
final _firestoreController = StreamController<Photography>();
Stream<Photography> get outFirestore => _firestoreController.stream;
Sink<Photography> get inFirestore => _firestoreController.sink;
void init() async{
photography = db.getPhotography();
inFirestore.add(photography);
}
#override
void dispose() {
_firestoreController.close();
}
}
my StreamBuilder Widget
How to get data using JSON serialization
child: StreamBuilder<Photography>(
stream: bloc.outFirestore,
initialData: null,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: buildItem(snapshot.data, bloc));
// children: snapshot.data.documents
// .map<Widget>((doc) => buildItem(doc, bloc))
// .toList());
} else {
return SizedBox();
}
}),
builderItem() method,
buildItem(Photography doc, PhotographyBloc bloc) {
...
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: FadeInImage.assetNetwork(
placeholder: "assets/images/photography.jpg",
image: doc.couplePhoto,
// image: doc.data['couplePhoto'],
fit: BoxFit.fill,
),
),
According to the package source :
/// Writes to the document referred to by this [DocumentReference].
///
/// If the document does not yet exist, it will be created.
///
/// If [merge] is true, the provided data will be merged into an
/// existing document instead of overwriting.
Future<void> setData(Map<String, dynamic> data, {bool merge = false}) {
return Firestore.channel.invokeMethod<void>(
'DocumentReference#setData',
<String, dynamic>{
'app': firestore.app.name,
'path': path,
'data': data,
'options': <String, bool>{'merge': merge},
},
);
}
You must give a <String, dynamic> Map to setData(x) method.
So in your case you should maybe do it like this :
getPhotography() async {
return db.collection('photography')
.document("0yUc5QBGHNNq6WK9CyyF")
.setData(photography.toJson());
}