How to retrieve all documents in a collection in Firebase and add to a list? - flutter

I have a collection in Firebase that I am trying to retrieve and add to a list:
I also have an events model defined. Before adding the event to a list, I would like to create an Event object using the data read from Firebase.
event_model:
class Event {
String eid;
String title;
String location;
String start;
String end;
String instructor;
String image;
String description;
Event({
required this.eid,
required this.title,
required this.location,
required this.start,
required this.end,
required this.instructor,
required this.image,
required this.description
});
String getEid() {
return eid;
}
String getTitle() {
return title;
}
String getLocation() {
return location;
}
String getStart() {
return start;
}
String getEnd() {
return end;
}
String getInstructor() {
return instructor;
}
String getImage() {
return image;
}
String getDescription() {
return description;
}
void setEid(String eid) {
this.eid = eid;
}
void setTitle(String title) {
this.title = title;
}
void setLocation(String location) {
this.location = location;
}
void setStart(String start) {
this.start = start;
}
void setEnd(String end) {
this.end = end;
}
void setInstructor(String instructor) {
this.instructor = instructor;
}
void setImage(String image) {
this.image = image;
}
void setDescription(String description) {
this.description = description;
}
}
This is what I have so far. I am creating the list of Event objects then trying to get the entire collection and for each document in the collection, I am creating the Event object and trying to add it to the list. I am not sure if this is correct.
List<Event> _events = [];
Future<UserProfile> getUserProfile() async {
try {
final FirebaseAuth auth = FirebaseAuth.instance;
final snapshot = await FirebaseFirestore.instance.collection('events').get();
snapshot.docs.forEach((doc) {
Map<String, dynamic>? data = snapshot.data();
Event event = Event(
eid: data?['eid'],
title: data?['title'],
...
});

a better approach for this is that the conversation of the Map<String, dynamic> to an Event class object, should be done using a factory constructor of the Event class, and setting a default value for each property so if something goes null, your app won't crash, it will have a default value and work fine, like this:
add this to your Event class:
factory Event.fromMap(Map<String, dynamic>? map) {
return Event(
eid: map?["eid"] ?? "defaultValue,"
title: map?["title"] ?? "defaultValue",
location: map?["location"] ?? "defaultValue",
start: map?["start"] ?? "defaultValue,"
end: map?["ends"] ?? "defaultValue,"
instructor: map?["instructor"] ?? "defaultValue,"
image: map?["image"] ?? "defaultValue,"
description: map?["description"] ?? "defaultValue",
);
}
then instead of implementing your methods, save yourself from the boilerplate code by using the:
Event event = Event.fromMap(snapshot.data() as Map<String, dynamic>);
_events.add(event);

Related

Riverpod Model from List<Model> to Map<String>

I am new to flutter and I am a bit confused about Riverpod and have wasted a few days on this issue which is probably really easy. I have a Model, Provider and Service created with Riverpod which I will share below. I have a widget that takes a Map and an API that is structured
{
"job": [
{"feild1": "data",..},
{"feild2": "data",..},
{"feild3": "data",..}
]
}
It is being mapped as List how can I change that to Map for a child widget I have created.
This is my Provider:
final jobsDataProvider = FutureProvider<List<JobsModel>>((ref) async {
return ref.watch(jobsProvider).getJobs();
});
This is my model:
class JobsModel {
final String jobid;
final String from_state;
final String from_suburb;
final String to_state;
final String to_suburb;
final String travel_time;
final String date;
final String distance;
final String status;
final String map;
JobsModel({
required this.jobid,
required this.from_state,
required this.from_suburb,
required this.to_state,
required this.to_suburb,
required this.travel_time,
required this.date,
required this.distance,
required this.status,
required this.map,
});
factory JobsModel.fromJson(Map<String, dynamic> json) {
return JobsModel(
jobid: json['jobid'],
from_state: json['from']['state'],
from_suburb: json['from']['suburb'],
to_state: json['to']['state'],
to_suburb: json['to']['suburb'],
travel_time: json['travel_time'],
date: json['date'],
distance: json['distance'],
status: json['status'],
map: json['map'],
);
}
}
This is my service:
class ApiServices {
String endpoint = 'https://localhost:3000/jobs';
Future<List<JobsModel>> getJobs() async {
Response response = await get(Uri.parse(endpoint));
if (response.statusCode == 200) {
final List result = jsonDecode(response.body)['jobs'];
return result.map(((e) => JobsModel.fromJson(e))).toList();
} else {
throw Exception(response.reasonPhrase);
}
}
}
final jobsProvider = Provider<ApiServices>((ref) => ApiServices());
My child widget takes a Map<String, dynamic> how can I make this work so I can map multiple widgets from the returned api call into a row.
Thanks heaps all.

type 'Null' is not a subtype of type 'List<RestaurantModel>'

I'm new to programming and currently learning JSON. I got this error when using Cubit to access the JSON:
RestaurantFailed(type 'Null' is not a subtype of type 'List<RestaurantModel>')
JSON Sample: https://restaurant-api.dicoding.dev/list
I'm trying to access the API and insert it to RestaurantModel.
this is my code:
restaurant_service.dart
class RestaurantService {
Future<List<RestaurantModel>> fetchAllData() async {
try {
Uri url = Uri.http('restaurant-api.dicoding.dev', '/list');
http.Response response = await http.get(url);
Map<String, dynamic> result = jsonDecode(response.body);
List<RestaurantModel> restaurants = result['restaurants'].forEach((json) {
return RestaurantModel.fromJson(json: json);
});
return restaurants;
} catch (e) {
rethrow;
}
}
}
restaurant_cubit.dart
class RestaurantCubit extends Cubit<RestaurantState> {
RestaurantCubit() : super(RestaurantInitial());
void fetchData() async {
try {
emit(RestaurantLoading());
List<RestaurantModel> restaurants =
await RestaurantService().fetchAllData();
emit(RestaurantSuccess(restaurants));
} catch (e) {
emit(RestaurantFailed(e.toString()));
}
}
}
restaurant_model.dart
class RestaurantModel {
final String id;
final String name;
final String description;
final String pictureId;
final String city;
final double rating;
String? address;
List<String>? categories;
List<String>? menus;
List<CustomerReviewModel>? customerReviews;
RestaurantModel({
required this.id,
required this.name,
required this.description,
required this.pictureId,
required this.city,
this.rating = 0.0,
this.address = '',
this.categories,
this.menus,
this.customerReviews,
});
factory RestaurantModel.fromJson({required Map<String, dynamic> json}) =>
RestaurantModel(
id: json['id'],
name: json['name'],
description: json['description'],
pictureId: json['pictureId'],
city: json['city'],
rating: json['rating'].toDouble(),
address: json['address'] ?? '',
categories: json['categories'] ?? [],
menus: json['menus'] ?? [],
customerReviews: json['customerReviews'] ?? [],
);
}
any feedback or input would be very appreciated! Cheers
The forEach should be replaced by map(...).toList() like the following code snippet:
List<RestaurantModel> restaurants = result['restaurants'].map((json) {
return RestaurantModel.fromJson(json: json);
}).toList();
This is because forEach returns void and it cannot be assigned to anything. On the other hand, map returns a Iterable<RestaurantModel> and it's just a matter of converting it to list with the toList() method.

Data gets lost when added to a Model

I am getting data from Firebase Database and Adding it to a List of my Model class. I tested the incoming data by printing to Console and it works fine, but once i add the data to my model class, it disappears.
Here's my Provider class where i'm loading the data.
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:local_stuffs_notification/apis/fcm.dart';
import 'package:local_stuffs_notification/models/request_model.dart';
import 'package:shared_preferences/shared_preferences.dart';
class IncomingRequest with ChangeNotifier {
List<RequestModel> _incomingRequests = [];
IncomingRequest(this._incomingRequests);
List<RequestModel> get incomingRequest {
return [..._incomingRequests];
}
Future<void> setIncomingRequest(RequestModel requestModel) async {
try {
DatabaseReference reference =
FirebaseDatabase.instance.ref("incomingRequests");
reference.child(requestModel.id).child(Fcm.getUid()).set(
{
"name": requestModel.name.toString(),
"phone": requestModel.phone.toString(),
"email": requestModel.email.toString(),
"fcmToken": requestModel.fcmToken.toString(),
},
);
notifyListeners();
} catch (error) {
rethrow;
}
}
Future<void> loadIncomingRequests() async {
try {
SharedPreferences preferences = await SharedPreferences.getInstance();
DatabaseReference reference = FirebaseDatabase.instance
.ref('incomingRequests/${preferences.getString('userId')!}');
Stream<DatabaseEvent> stream = reference.onValue;
stream.listen((DatabaseEvent event) {
print(event.snapshot.value);
final data = event.snapshot.value as Map;
print('data: $data');
final List<RequestModel> loadedRequest = [];
data.forEach(
(key, value) {
print('requestData: ${value['name']}');
loadedRequest.add(
RequestModel(
id: key.toString(),
name: value['name'].toString(),
fcmToken: value['fcmToken'].toString(),
phone: value['phone'].toString(),
email: value['email'].toString(),
),
);
print(loadedRequest);
},
);
_incomingRequests = loadedRequest;
print('LoadedRequests: $loadedRequest');
notifyListeners();
});
// reference.onValue.listen(
// (event) {
// if (event.snapshot.value == null) {
// return;
// }
// final data = event.snapshot.value as Map;
// final List<RequestModel> loadedRequests = [];
// data.forEach(
// (key, requestData) {
// loadedRequests.add(
// RequestModel(
// id: key,
// name: requestData['name'],
// fcmToken: requestData['fcmToken'],
// phone: requestData['phone'],
// email: requestData['email'],
// ),
// );
// },
// );
// _incomingRequests = loadedRequests;
// notifyListeners();
// },
//);
} catch (error) {
rethrow;
}
}
}
Here's my Model Class
class RequestModel {
final String id;
final String name;
final String fcmToken;
final String phone;
final String email;
RequestModel({
required this.id,
required this.name,
required this.fcmToken,
required this.phone,
required this.email,
});
}
I'm getting the data until i added it to loadedRequest List
Please help, i've spent hours on this and i don't know what i'm doing wrong. When i print the loadedRequest list, i get an empty list. Thanks.
Those logs aren't showing an empty list - It says [Instance of 'RequestModel']. That means there is a value there, but Dart simply doesn't know how to convert RequestModel to a String so that it can be printed out on the console.
An empty list would be printed simply as [], and if you had two values, for example, you would see [Instance of 'RequestModel', Instance of 'RequestModel'].
To print out your values with more detail, you can override the toString() method on your class.
For example:
class RequestModel {
final String id;
final String name;
final String fcmToken;
final String phone;
final String email;
RequestModel({
required this.id,
required this.name,
required this.fcmToken,
required this.phone,
required this.email,
});
#override
String toString() =>
"RequestModel(id: $id, name: $name, fcmToken: $fcmToken, phone: $phone, email: $email)";
}
take a look at the raw data once again, it contains all the users data so you need to get the access the uid before the name
final uid = FirebaseAuth.instance.currentUser!.uid;
and then for the RequestModel:
name: data[uid]['name']

Objectbox doesn't store nullable value in one to many relation

I have this one to many relation which looks like this.
#Entity()
class Product {
#Id()
int id;
String name;
String category;
int categoryId;
WeightType weightType;
double? price;
double weight;
bool isRefunded;
Product({
this.id = 0,
required this.name,
required this.category,
required this.categoryId,
this.weightType = WeightType.kg,
this.price,
this.weight = 0,
this.isRefunded = false,
});
Product copy() => Product(
name: name,
category: category,
id: id,
price: price,
weight: weight,
weightType: weightType,
categoryId: categoryId,
isRefunded: isRefunded,
);
String getInfo(int index) {
switch (index) {
case 1:
return name;
case 2:
return price.toString();
case 3:
return weight.toString() + ' ' + weightType.getName();
case 4:
if (weightType == WeightType.kg) {
return (price! * weight).toString();
} else {
return (price! * (weight / 1000)).toString();
}
default:
return '';
}
}
}
#Entity()
class Pdf {
#Id()
int id;
Uint8List pdfData;
final String customerName;
#Property(type: PropertyType.date)
final DateTime purchaseDate;
ToMany<Product> products; //<----------- the relation
double totalAmount;
PaymentStatus paymentStatus;
#Property(type: PropertyType.date)
DateTime? updateDate;
Pdf({
this.id = 0,
required this.pdfData,
required this.purchaseDate,
required this.customerName,
required this.totalAmount,
required this.products,
this.paymentStatus = PaymentStatus.unPaid,
this.updateDate,
});
int get status => paymentStatus.index;
set status(int value) {
paymentStatus = PaymentStatus.values[value];
}
}
now how I am adding data to db,
void addToDb(Bill bill){
final billPdf = Pdf(
pdfData: pdf,
purchaseDate: DateTime.now(),
customerName: bill.customerName,
totalAmount: bill.totalAmount,
paymentStatus: bill.paymentStatus,
products: obj.ToMany(items: bill.products) //<------- this is the product list
);
DBService.pdfBox.put(billPdf,mode: obj.PutMode.insert);
}
now before executing Dbservice.pdfbox.put()
afterexecuting Dbservice.pdfbox.put()
you can see that price and weight are not null and also RelInfo is also not null(it has one parameter which is null)
Now when I fetch data from db, I get all the data same as I put except the nullable variables or a variable which contains default value. which are price, weight and 'weightType`.
which you can see here,
Lastly, this how I created objectbox store,
class DBService {
static late final Store store;
static late final Box<Product> productBox;
static late final Box<Category> categoryBox;
static late final Box<Pdf> pdfBox;
DBService._();
static Future<void> create() async {
store = await openStore();
productBox = store.box();
categoryBox = store.box();
pdfBox = store.box();
}
}
calling create() in main()
If I put final pdf = ToOne<Pdf>() in product model then question which I asked earlier happens all over again.
which is this -> how to add same object in objectbox's box multiple times?
Now can you guys tell me why only nullable/which has default values are not getting stored in objectbox db?

Firestore collection map to list

Hi I need to retrieve all documents from firestore collection with this:
EventList<Event>testdata(QuerySnapshot snapshot) {
return snapshot.docs.map((data) => EventList<Event>(events: {
data['date']: [
Event(
date: data['date'], title: data['name'], icon: Icon(
Icons.block,
color: Colors.red[200],
size: 30,
)),
]
})).toList();
}
Stream<EventList<Event>> get caldendardata {
return events.snapshots().map(testdata);
}
but i get this error: A value of type 'List<EventList<Event>>' can't be returned from the method 'testdata' because it has a return type of 'EventList<Event>'.
The Firestore :
I'm using this package to add calendar to my app it requires the event on the calendar to be {EventList<Event>? markedDatesMap} .
EventList form the package:
class EventList<T> {
Map<DateTime, List<T>> events;
EventList({
required this.events,
});
void add(DateTime date, T event) {
final eventsOfDate = events[date];
if (eventsOfDate == null)
events[date] = [event];
else
eventsOfDate.add(event);
}
void addAll(DateTime date, List<T> events) {
final eventsOfDate = this.events[date];
if (eventsOfDate == null)
this.events[date] = events;
else
eventsOfDate.addAll(events);
}
bool remove(DateTime date, T event) {
final eventsOfDate = events[date];
return eventsOfDate != null ? eventsOfDate.remove(event) : false;
}
List<T> removeAll(DateTime date) {
return events.remove(date) ?? [];
}
void clear() {
events.clear();
}
List<T> getEvents(DateTime date) {
return events[date] ?? [];
}
}
Event form the package:
class Event implements EventInterface {
final DateTime date;
final String? title;
final Widget? icon;
final Widget? dot;
final int? id;
Event({
this.id,
required this.date,
this.title,
this.icon,
this.dot,
});
#override
bool operator ==(dynamic other) {
return this.date == other.date &&
this.title == other.title &&
this.icon == other.icon &&
this.dot == other.dot &&
this.id == other.id;
}
#override
int get hashCode => hashValues(date, title, icon, id);
#override
DateTime getDate() {
return date;
}
#override
int? getId() {
return id;
}
#override
Widget? getDot() {
return dot;
}
#override
Widget? getIcon() {
return icon;
}
#override
String? getTitle() {
return title;
}
}
abstract class EventInterface {
DateTime getDate();
String? getTitle();
Widget? getIcon();
Widget? getDot();
int? getId();
}
I would appreciate a little help here.
Thank you in advance
map returns a List. That's why you have a List<EventList>.
I believe you are trying to flatten the list so that you instead have a single EventList with all of the events. One way to accomplish this is to use fold.
Here is an example that you should be able to apply to your code. One could paste this into Dartpad to quickly see how it works:
class Event {
const Event(this.id);
final int id;
}
class EventList {
const EventList({required this.events});
final List<Event> events;
}
class FirebaseData {
const FirebaseData(this.docs);
final List<Event> docs;
}
void main() {
// Simulating your data stream
final FirebaseData snapshot = FirebaseData(List.generate(5, (index) => Event(index)));
// What you are returning from your code currently
final List<EventList> eventListList =
snapshot.docs.map((data) => EventList(events: [data])).toList();
// What you actually want to return from your code
final EventList eventList = eventListList.fold(EventList(events: []),
(previousValue, element) => EventList(events: previousValue.events..addAll(element.events)));
print(eventList.events);
}
When performing toList you getting a List<EventList<Event>> each EventList with one event.
I think you want to get a List<Map> from Firestore to later build your class.
You can achieve that with a code like this.
EventList<Event>testdata(QuerySnapshot snapshot) {
//Get all data
final List<Map> eventListMap = snapshot.docs.map((data) => {
data['date']: [
Event(
date: data['date'], title: data['name'], icon: Icon(
Icons.block,
color: Colors.red[200],
size: 30,
)),
]
}).toList();
//Join to single Map, it should not contain repeated keys (date) as one of them would be lost
final Map eventsMap = eventsData.fold({},(map1, map2) => map1..addAll(map2));
//Return your class
return EventList<Event>(events: eventsMap);
}
Stream<EventList<Event>> get caldendardata {
return events.snapshots().map(testdata);
}
I did not try it and you can rename or change anything.