I want to migrate my iOS app from CloudKit to Firestore. Most of the architectural changes are simple, but there is one caveat that I do not understand how to overcome.
In CloudKit, I can save a change token on the client and pass it to CloudKit functions when fetching or saving data. During fetch, it ensures that I receive only those updates, which I did not previously receive. During save, unless my change token corresponds to the latest available one, the operation fails until I perform a fetch first. In my case, this design is ideal.
Unfortunately, it seems like such functionality is not available out of the box in Firestore. For example, when attaching a listener to document snapshot, there is no way to tell Firestore "Here is a token X corresponding to the last update which I have and I want to receive all updates following it".
I could theoretically implement the change token functionality myself using cloud functions. The issue, however, is that two devices can try to perform an update simultaneously and I would need to find a way to ensure that one of these updates is executed in full before the other one starts (so that one of them fails with "invalid change token").
It sounds to me like a common design pattern, but I am struggling to find any ideas as to how to implement it correctly in Firebase.
Hence, I would like to ask you for suggestions on how I should proceed with migrating the "change token" functionality from CloudKit to Firestore.
Note, I am making my first steps in backend development, so my terminology is far from perfect.
Related
I am working on a simple app that allows users to search for something using an API and save it to view later.
However, I don't want to integrate authentication in the app. I can, but would rather not as a UX decision. Do you know of a way to generate a device token, that is unique to every device and can be used to store which assets a device has saved in the db?
I am thinking of expo push tokens as a possible solution, but that would require users to accept push notifications - so what happens if a user says no?
Sounds like you could just use react-native-uid to generate a unique id for your device and then store it in AsyncStorage and fetch it from there going forward.
For more inspiration, or perhaps just a more canonical way to do this... read up on suggestions surroundings the recently deprecated constant for installationId here:
https://docs.expo.dev/versions/latest/sdk/constants
I haven't used this before but if you're looking for something bullet proof then this is probably your goal of getting the same concept.
Firebase Anonymous Authentication might be ideal to use in this case. This can be used to create a user in Firebase auth without any credentials and can be useful especially when you are using either of Firebase's databases since you can use security rules with user's UIDs.
However, once the user logs out of the account by any means including but not limited to using sign out option in your app, clearing app data or uninstalling the app, the same account with that UID cannot be recovered. I looked up for AsyncStorage and apparently that gets cleared to if the app is deleted.
We are building an app for our teams out in the field that they collect their daily information using Firebase. However one of our concerns is poor connectivity. We are looking to build an Online/Offline button they can click to essentially work offline for when things slow down. We've built a workflow in which we query all the relevant information from Firestore.
I wanted to know if there was a way to tell Firestore to work directly on the cache only and not try to hit the servers directly. I don't want Firestore attempting to make server calls until they enable online again.
You shouldn't need to do this. If you use realtime listeners, they will already first return the data from the local cache, and only then reach out to the server to check for updates.
If you are performing one-time reads, the SDK will by default try to reach the server first (since it has only one chance to give you a value). If you want it to only check the local cache, you can pass an argument to the get call to do so.
You can also disable the network completely, in which case the client will never call on the network and only serve from the local cache. I recommend reading about that and more in the documentation on using Firestore offline.
(this was originally a post on the flutter-dev reddit that was redirected here)
So I started making this flutter app using firebase as a backend and after looking at all the options for state management I finally realised that the tools provided by firebase already handle pretty much everything I would need state management for.
For example:
I could set the currently logged in user in my state to show the right login or home page and make the user uid available to widgets for their firestore API calls.
OR
I can just listen to FirebaseAuth.instance.onAuthStateChanged to show the right page and just use FirebaseAuth.instance.currentUser() from anywhere to get the logged in user uid and do my firestore calls.
What I mean is, for every thing that would require global state, I can just basically have a firebase stream listener.
Is this right ? Or am I missing something here ?
You're not missing anything. Since most Firebase APIs rely on data from Google's servers, many of them are designed to be used in a reactive way. Making your UI reactively respond to those asynchronous changes is (in my experience) the best way to keep your code simple.
There may be slight behavior between the different types of listeners. But the onAuthStateChanged listener immediately fires with the current state when you attach it, which makes it a good example of a listener that you can use everywhere you need to respond to auth state (instead of also storing that state somewhere in your app).
In that scenario I would say yes, you can read the onAuthStateChanged stream and react to changes. But there are also scenarios where I need a stream for interacting between widgets without a parent/child relationship. For example, in one of my apps I have a company selector, and the rest of the app reflects to the selected company. I created a stream, so that the company selector doesn't need to be a parent of the other widgets, and especially so that I don't need to pass the company parameter to all the widget tree.
I also have one scenario where I need to load extra information about the user that isn't available on the FirebaseUser object. So when the user is logged on I load their information from a "users" collection and then I add that to a custom stream.
So to conclude I would say yes, you should use the default Firebase streams when possible, but that doesn't mean you can or should use that solution for everything.
The following quote is taken from Google Developer Blog. https://developers.googleblog.com/2017/08/hamilton-app-takes-stage.html
For example, when someone enters the lottery, the app first writes
data to specific nodes in Realtime Database and the database's
security rules help to ensure that the data is valid. The write
triggers a Cloud Function, which runs business logic and stores its
result to a new node in the Realtime Database. The newly written
result data is then pushed automatically to the app.
I understand that instead of making a request and expecting a request, they followed the following pattern:
Write to real-time database
The write trigger an action
The action triggers some logic and function
The function writes data back to the database
The listener on the app gets a notification about the data written on 4.
App updates UI or takes whatever action.
Hamilton app followed this pattern when someone enters the competition for a ticket. My main concern with the pattern is that if the connection is offline, we won't get results and at the same time we won't get an error. The changes will be updated locally and firebase will update the next time there is a connection, which will make things messy, as the user is expecting a result.
I was wondering if there is an easy way or a pattern to follow, so we will get an error and display it.
The first solution that comes to my mind is to somehow add a time on the save operation. If the save is not done on the live database within x seconds then we display an error. At the same time, we will also need a timeout on the reply. So if we do not get a reply within x seconds we display something to the user.
I hope I am not confused as I did not understand the pattern well. I struggled with the title, so if anyone can improve it, please be my guest.
Even building an offline capable app, if an operations chain relies on a Cloud Function, I'd make it impossible to execute while offline, telling user to retry when he's online.
However this might lead to bad situations as well because you need to check connection every time the user tries to execute the ops, which might be painful.
Let's say I'm developing app like Instagram: for iOS, Android and Web. I decided to use Google Firebase as it really seems to simplify the work.
The features user needs in the app are:
Authorization/Registration
Uploading photos
Searching for other people, following them and see their photos
I come from traditional "own-backend" development where I do need to setup a server, create database and finally write the API to let the frontend retrieve the data from the server. That's the reason why it's unclear to me how it all works in Firebase.
So the question is how can I create such app:
Should I create my own API with cloud functions? Or it's ok to work with the database directly from the client-side?
If I work with the database directly why do I need cloud functions? Should I use them?
Sorry for such silly questions, but it is really hard to get from scratch.
The main difference between Firebase and the traditional setup you describe is that with Firebase, as far as the app developer is concerned, the client has direct access to the database, without the need for an intermediate custom API layer. Firebase provides SDKs in various languages that you would typically use to fetch the data you need / commit data updates.
You also have admin SDKs that you can use server-side, but these are meant for you to run some custom business logic - such as analytics, caching in an external service, for exemple - not for you to implement a data fetching API layer.
This has 2 important consequences:
You must define security rules to control who is allowed to read/write at what paths in your database. These security rules are defined at the project level, and rely on the authenticated user (using Firebase Authentication). Typically, if you store the user profile at the path users/$userId, you would define a rule saying that this node can be written to only if the authenticated user has an id of $userId.
You must structure your data in a way that makes it easily readable - without the need for complex database operations such as JOINs that are not supported by Firebase (you do have some limited querying options tough).
These 2 points allow you to skip the 2 main roles of traditional APIs: validating access and fetching/formatting the data.
Cloud functions allow you to react to data changes. Let's say everytime a new user is created, you want to send him a Welcome email: you could define a cloud function sending this email everytime a new node is appended to the users path. They allow you to run the code you would typically run server-side when writes happen, so they can have a very broad range of use-cases: side-effects (such as sending an email), caching data in an external service, caching data within Firebase for easier reads, analytics, etc..
You don't really need a server, you can access the database directly from the client, as long as your users are authenticated and you have defined reasonable security rules on Firebase.
In your use case you could, for example, use cloud functions to create a thumbnail when someone uploads a photo (Firebase Cloud Functions has ImageMagick included for that), or to denormalize your data so your application is faster, or to generate logs. So, basically you can use them whenever you need to do some server side processing when something changes on your database or storage. But I find cloud functions hard to develop and debug, and there are alternatives such as creating a Node application that subscribes to real time changes in your data and processes it. The downside is that you need to host it outside Firebase.
My answer is definitely NOT complete or professional, but here are the reasons why I choose Cloud Functions
Performance
You mentioned that you're writing an instagram-like mobile device app, then I assume that people can comment on others' pictures, as well as view those comments. How would you like to download comments from database and display them on users' devices? I mean, there could be hundreds, maybe thousands of comments on 1 post, you'll need to paginate your results. Why not let the server do all the hard work, free up users' devices and wait for the results? This doesn't seem like a lot better, but let's face it, if your app is incredibly successful, you'll have millions of users, millions of comments that you need to deal with, server will do those hard jobs way better than a mobile phone.
Security
If your project is small, then it's true that you won't worry about performance, but what about security? If you do everything on client side, you're basically allowing every device to connect to your database, meaning that every device can read from/write into your database. Once a malicious user have found out your database url, all he has to do is to
firebase.database().ref(...).remove();
With 1 line of code, you'll lose all your data. Okay, if you say, then I'll just come up with some good security rules like the one below:
This means that for each post, only the owner of that post can make any changes to it or read from it, other people are forbidden to do anything. It's good, but not realistic. People are supposed to be able to comment on the post, that's modifying the post, this rule will not apply to the situation. But again, if you let everybody read/write, it's not safe again. Then, why not just make .read and .write false, like this:
It's 100% safe, because nobody can do anything about anything in your database. Then, you write an API to do all the operations to your database. API limits the operations that can be done to your database. And you have experience in writing APIs, I'm sure you can do something to make your API strong in terms of security, for example, if a user wants to delete a post that he created, in your deletePost API, you're supposed to authenticate the user first. This way, 'nobody' can cause any damage to your database.