Firebase Analytics does not show AnalyticsEventItem - flutter

In a Flutter application, I'm trying to track some user activities. The app uses GoogleAnalytics and some Firebase features. Thus it uses Firebase Analytics to measure statistics.
For example, I would like to see which item (product) added wishlist mostly. So I wrote this code to track if user add a specific item to their wishlist:
Future<void> addToWishlist({
required int productId,
required String name,
required int brandId,
required int categoryId,
required double price,
}) async {
var analyticsEventItem = AnalyticsEventItem(
itemId: productId.toString(),
itemName: name,
itemBrand: brandId.toString(),
itemCategory: categoryId.toString(),
);
await FirebaseAnalytics.instance.logAddToWishlist(
value: price,
currency: "USD",
items: [analyticsEventItem],
);
}
In dashboard, it shows value and currency correctly but I could not find any item under add_to_wishlist event.
Is there something wrong I did? Where can I see these items?

firebaser here
Currently, item parameter is not yet supported to be displayed in the Analytics dashboard. However, you can access this data in BigQuery export.

Related

Handling of Firestore IDs in models and entities

I'm learning domain driven design for Flutter apps. I understand that the model is used between the infrastructure layer and the use-case, and the entity is used between in the use case and the UI.
Let's say that my app is dealing with books and I'm storing my books in Cloud Firestore. I have defined a very simple BookEntity with an id and a title.
#freezed
class BookEntity with _$BookEntity {
const factory BookEntity({
required String firestoreId, // This is the ID of the document in firestore
required String title,
}) = _BookEntity;
}
I believe that the ID of the document should be in the entity, because if I need to modify this book in the Firestore I will need to know the reference of the document, right?
As you know, in firestore the id is not part of the data themselves. When I read my database, I would be using the code below.
// code not tested
FirebaseFirestore
.instance
.collection('books')
.doc('uXSin0z3gqPHwhVLCP98')
.get()
.then((DocumentSnapshot<Map<String, dynamic>> snapshot) {
snapshot.id; // -> 'uXSin0z3gqPHwhVLCP98'
snapshot.data(); // -> {title: 'To Kill a Mockingbird', price: 11.69, year: 1960}
});
Somewhere, I will need to put the id together with the data. I think the right place to do it is in the model, which is created in the repository. I think my model would probably look very similar to the Entity:
#freezed
class BookModel with _$BookModel {
const factory BookModel({
required String firestoreId,
required String title,
}) = _BookModel;
}
And, when fetching data from Firestore I would create a model with:
BookModel(
firestoreId: snapshot.id,
title: snapshot.data()?['title'],
);
This can then be converted to a BookEntity which will be consumed by the UI.
The problem is that when I am reversing the flow, when I am creating a new book, the ID of the firestore document is not known in the presentation and domain layers. Therefore my BookEntity and BookModel must be updated so that the id is optional. The entity and the model now look like this
#freezed
class BookEntity with _$BookEntity {
const factory BookEntity({
String? firestoreId,
required String title,
}) = _BookEntity;
}
#freezed
class BookModel with _$BookModel {
const factory BookModel({
String? firestoreId,
required String title,
}) = _BookModel;
}
The problem is that now, every time I need to access the firestoreId field of my BookEntity, whose data originate from Firestore, I need to test whether the firestoreId field is null or not. But it cannot be null because the data come from Firestore, so there is always an ID. So I will either write a lot of null-checks, or use the ! (which I don't like).
In short, the "upstream" and "downstream" flows have different requirements for the firestoreId field. The Firebase -> UI flow needs a String, and the UI -> Firebase flow needs a String?.
So the question is what is the best and cleanest way to handle this?
firestoreId should be nullable, because it make sense for BookEntity to have firestoreId sometime and not have firestoreId sometime.
You probably don't need to use firestoreId in the UI, and It's only needed when writing to the Firestore. So you can have a writeToFirestore method and only use a single null check there.
You can also generate a new random id locally whenever a new BookEntity is created. Using your own document id when creating a document in Firestore
One more solution is to use late final String firestoreId, but it's skipping null check making debuging harder and doesn't work with Freezed.

How can I send and receive two types of data from one single field in firestore for flutter?

I have two textfields in my flutter app. I used showDatePicker on the first one when tapped and showTimePicker on the second one when tapped (to let a user choose a certain date and time).
I am using a class model to send and receive date and time both from one variable:
DateTime? taskDate; // Time as well
I thought about sending them separately but look what happens in firebase when I send them both together :
One timestamp field that has date and time seperated ( like a map).
My question is how can I point to one of them ( date or time) in Firestore so I can do some actions on them?
For example I want to add a task in 13 January 20203 and I want this to be saved in date field and the time to be saved in time field. What you see in the picture isn't picked date and time.
This is the code of sending data using a model and bloc state managment :
TaskModel? taskModel;
void addTask({
required String title,
required DateTime date,
}) {
emit(AddPostLoadingState());
taskModel = TaskModel(
taskTitle: title,
taskDate: date,
name: userModel!.name,
uid: userModel!.uId);
FirebaseFirestore.instance
.collection('tasks')
.add(taskModel!.toJson())
.then((value) {
getTask();
}).catchError((error) {
print(error);
emit(AddPostErrorState(error));
});
}
And since I know that datetime recieved as timestamp in firestore .. I changed it in the type when I get data :
void getTask() {
emit(GetTaskLoadingState());
FirebaseFirestore.instance
.collection('tasks')
.where('uid', isEqualTo: uId)
.orderBy('taskDate')
.get()
.then((value) {
tasks = []; // Global List<TaskModel>
for (var element in value.docs) {
data['taskDate'] = DateTime.fromMillisecondsSinceEpoch(
data['taskDate'].seconds * 1000);
tasks.add(TaskModel.fromJson(data));
}
emit(GetTaskSuccessState());
}).catchError((error) {
print(error.toString());
emit(GetTaskErrorState(error: error));
});
}
The Firestore Timestamp field is an atomic value that contains both date and time. There is no way to set the date and time of the field separately, as it's stored as a single value in the database.
If you want to manipulate the date and time separate in the database, consider storing them as separate fields (e.g. in a map as you already describe) yourself. Then you'll need to convert to/from a DateTime in your code when reading from/writing to the database.

How to make a WishedProduct Widget using FirebaseFirestore

I'm making a product display app.
I'm going to create a "Wish List" widget that brings up the product that the user picked as 'wish item'.
I structured it as shown in picture 1.
And to create a widget,
I get all the documents of the collection('wish') of doc('User A').
And with their String values(product name) of the doc,
Get the product data from the collection ('Product') using Query.
The product collection is shown in the picture below.
Is there a more efficient way?
I thought it might be easier to change the data structure.
However, even if I create a new Collection('Wish'), at the same level as Collection('Product'), and put the product's name and user's e-mail in it,
I need to focus on the 'Product' collection with the 'Product name'.
Because I have to use the price, brand, name of product, in Collection('Product').
Is there any other efficient way I'm missing? Thank you!
Instead of storing the product name in string you can use reference type to store direct reference of the document inside other document.
Example code
DocumentReference ref = db.collection('products').doc('product-document-id');
Map<String,dynamic> data = {
'name' : 'Product A',
'product_ref' : ref, // Product document Reference
};
db.collection("users")
.doc("user-id")
.collection("wish")
.doc("your_product_name")
.set(data);
This will add the wish product with dynamic id.
Now you can directly read the document using the product_ref. You can use something like this
final docRef = db.collection("users")
.doc("user-id")
.collection("wish").doc("your_product_name");
final docSnapshot = await docRef.get();
if (docSnapshot.exists) {
final data = doc.data() as Map<String, dynamic>;
var productRef = data?['product_ref'];
productRef.get().then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
// you will get your product here
}
});
}

How to add new data to firebase

How to add new data to firebase, in the picture on the second column there are users, and on the last one there is my note. This note whas created when user created account, and it whas updated when user logged in, before whas "bad location" etc. My problem is to add new note like this, not update it, kepp it, and at the same time, in the same column have some kind of "new collection" with the same 3 strings, but with different data.
class DataService {
final String uid;
DataService({required this.uid});
final CollectionReference notesCollection =
FirebaseFirestore.instance.collection('Notes');
Future createUserData(String notes, String localisation, String title) async {
return await notesCollection.doc(uid).set({
'notes': notes,
'title': title,
'localisation': localisation,
});
}
Future addData(String notes, String localisation, String title) async {
return await notesCollection.doc(uid).set({
'notes': notes,
'title': title,
'localisation': localisation,
});
}
}
This class shows my createUserData, when my user creates account or loggs in, but how to change "addData" in order to have logic as I described above?
if I understand correctly you want to create a new collection for each documents as history. try this:
notesCollection.doc(uid).collection("historic").add({
'notes': notes,
'title': title,
'localisation': localisation,});

Flutter - Multiple bodies for one home_page

So in my flutter app, when it's open, i get the type of the user from my RestApi (without account, with account type1, account type2 account type3), and each one of them has a special home_page (same appBar but different bodies with some similarities).
How can i do that ?
Do i have to create a specific route for each one ?
Yes the best practice is to create a route for each user, otherwise you will end up with 4 if blocks in the same file and things will get messy fast
You can create a generic widget how should a home_page should look, then create a model what information is different between users, create another file with all the data from where you get information that is related to the previous model.
Example:
Model:
class Model {
final String id;
final String name;
final String surname;
Model({this.id, this.name, this.surname});
}
Data:
const User_Data = [
Model(
id: 'user1',
name: 'Name1',
surname: 'Surname1',
),
Model(
id: 'user2',
name: 'Name1',
surname: 'Surname1',
),
];
In the data you can fetch the user by id by using User_Data.firstWhere() to get user id and after get all data for that user id.
final userId = ModalRoute.of(context).settings.arguments as String;
final selectedUser =
User_Data.firstWhere((user) => userId == user.id);