0

In my Flutter app, I have a userData collection on Cloud Firestore where I store user's data including name, image url, etc.. The user can create posts, add comments to post, etc. similar to any other social apps out there and so I have multiple other collections where the user's info is stored including the link to their profile image.

Let's say if the user adds a comment to a post, I save their name, profile image url and comment text as a document inside "postComment" collection and then I display his/her profile image, name and the comment text on the screen by reading this collection and document.

Now, if the user updates their profile image or even their name which will be reflected in the userData collection, I need to make sure that their name and image url are updated in all other collections as well.

What's the easiest and least costly way to do that? Do I need to loop through all my collections and their documents and update the field values, or is there like a simple cloud function that can handle this?

Thanks!

AkbarB
  • 460
  • 7
  • 24
  • This is indeed just part of the nature of the joy and pain of NoSql. Yes, you have to find EVERY use and change it. (Of course, as DougS explains, it may be more sensible to have another level of reference which you can change as needed.) – Fattie Aug 18 '20 at 15:12

3 Answers3

2

I also store user profile images in Firestore Storage, BUT I use a very consistent schema to make the images easy to "guess": When I have a document such as "/People/{userID}", and within the document is a field "image" which stores the URL to the image... ...then I store it in Firestore at the path "People/{userID/image/image.jpg" (eg). This way it is trivial to generate a StorageRef to it, and a downloadURL.

All the other uses of it always are to the now-standardized URL. Change the image in Storage; all references update.

For most "user" applications, the only use of the image is to feed it to a web-page, so just the URL is needed, and let the browser do the rest of the work.

As Fattie somewhat more aggressively stated, generally all you need is the URL. But following by itself that means you still would have to find all the references and update them if the user changes the URL. Saving a copy in Firestore Storage, and using that consistent URL, means all references will be "updated" just by changing what is stored at that location. Disadvantage is it will count as a storage read when fetched.

I'm finding duplicating data in NoSQL is great when it's fairly static - created once, and not dynamically changed (which is a LOT of cases). If your application doesn't fit that, it's better to store a reference to the source-of-truth, and incur the cost of the "lookup".

Here's a couple utilities I use to make this easier:

export const makeStorageRefFromRecord = (
  record,
  key = null,
  filename = null
) => {
  return FirebaseStorage.ref(
    record.ref.path + (key ? "/" + key : "") + (filename ? "/" + filename : "")
  );
};

export const makeFileURLFromRecord = (record, key = null, filename = null) => {
  return FirebaseStorage.ref(
    record.ref.path + (key ? "/" + key : "") + (filename ? "/" + filename : "")
  ).getDownloadURL();
};

("key" is essentially the fieldname) remember the refpath is a string of the "/" separated collection/document path to the record, and is completely knowable in a simple situation, such as "People/{userID}". If you keep this internal, you can use "filename" as simple as "image.jpg" so it's always the same - it's unique, because of the path.

LeadDreamer
  • 3,303
  • 2
  • 15
  • 18
  • but the download url is generated randomly . Right? I can define the image name (let's say uid.jpg) and then getDownloadURL and store it in user collection, but it looks to me that the url is a randomly generated link which is different from image name and it is going to be different if the image is updated in the Cloud Storage. the DownloadURL is different now – AkbarB Aug 18 '20 at 16:16
  • Well, sort of; the URL is short-lived and somewhat random, BUT you can generate it from entirely static data without a database call; see editted answer – LeadDreamer Aug 18 '20 at 17:33
  • I should also add that you *can* ***choose*** the filename to use to store the image; you don't have to keep the same name as the source file. – LeadDreamer Aug 18 '20 at 21:44
0

Do I need to loop through all my collections and their documents and update the field values

Minimally, yes, that's what you have to do.

or is there like a simple cloud function that can handle this?

You can certainly write your own Cloud Function to do this as well. There is not an existing function that will just do what you want - you have to code it.

Alternatively, you can just store the URL is one document, store the ID of that document in the other documents that need to refer to it, and have the client make an query for the single document with the URL you need.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • @Fattie Sorry, I don't understand your comment. The question was clear, and I believe the answer is similarly clear. I'll accept any suggestion for an improved answer. – Doug Stevenson Aug 18 '20 at 15:15
  • @Doug For your last alternative, are you suggesting to get the url using let's say a Futurebuilder? Can you elaborate? Thanks! – AkbarB Aug 18 '20 at 15:24
  • You can use whatever mechanism you wan to get the document with the related url. It shouldn't matter from a database design perspective. If you're having problems getting some specific code to work, I suggest posting a new question with the details of what you're trying to do and what isn't working the way you expect. – Doug Stevenson Aug 18 '20 at 15:28
  • @Doug This is actually a follow up question to the question that I posted yesterday. Maybe you can take a look and see if you can help: https://stackoverflow.com/questions/63443381/listview-builder-scrolling-in-flutter-not-smooth-when-combining-futurebuilder-an – AkbarB Aug 18 '20 at 15:33
0

There are multiple ways to do that.
The best way to do that is instead of storing the profile picture image again and again, you can store document references. If you are storing the images as base64, this would also save a lost of space and is cost efficient.

Another way of doing it is less efficient but you can store the image in firestore and refer it from there.
Both of these are from refereces
The last way of doing it and probably the most inefficient is by querying. You can go to that collection of post (Or if you store each post as a collection, loop through all of them) and then add a where filter and search for the imageURL or more safely a unique ID and then you can change them all one by one These are the ways that I know

Siddharth Agrawal
  • 2,960
  • 3
  • 16
  • 37
  • Hi, can you elaborate on the first alternative? I am storing the profile image on Firebase storage and a reference to that inside the user's document in userData collection. When the user updates their image, I delete and replace the image on Firebase Storage and update the link in the user document inside userData collection – AkbarB Aug 18 '20 at 15:26
  • Siddarth, the user is SURELY currently just storing a URL. What the user is asking, is that the URL has changed. The user would not be storing an actual image as binary. – Fattie Aug 18 '20 at 15:34
  • @AkbarB Sory for the late response. I would recommend you to use the document reference method. Store document references instead of the image url and name itself in the comment document. Then whenever you change the main file, the comment file will also change. – Siddharth Agrawal Aug 19 '20 at 12:47