⬆️ ⬇️

We write server-assistant for BaaS or “Well, why do I need Firebase then?”

Foreword



I am a beginner Android developer, I have about 1.5 years of experience in this field. I took on a rather big project, there is no one except for me in the team, and I can't write a backend. It was decided to choose Firebase as a platform. Since the specifics of my application required constant work and receiving data from the database in the background, I simply inserted all the EventListeners into the service and was satisfied. Until the very moment when I decided to write an iOS version. Having learned Swift, I rushed into battle. The Firebase SDK benefit turned out to be very good and similar for both systems, so I quickly wrote the main part and ... Why doesn't it work?



The essence of the problem and the formulation of the problem



iOS, to put it mildly, does not respect applications running in the background. The only way to awaken the application that the system killed (and it kills them because of any sneeze) - notifications via APNS. In addition, on Android 6+, the persistent connection does not hold and notifications eventually come with a delay of 5 minutes to 2 hours (7.1), if they are not implemented via GCM. It’s good that Firebase Cloud Messaging supports both APNS and GCM. Bad that for this you need an additional server. It would be cool if the notifications were automatically sent for certain changes in the database. Engineers promise to do something similar next year ... But it should work now.



Actually, not everyone has the desire / knowledge / resources to implement a full-fledged server with authorization and XMPP. So, we have two problems - the authorization of the user who wants to send a push and actually send it. This is in my case. If you just need to track the appearance of new data in the database (for example, articles) and send a notification to everyone who is subscribed to this topic - it is still easier.



Training



Initially, everything was written in Python, but the situation happened the same from a recent article .

On Python, there were problems with re-opening the device for reading - the second time the data was no longer read. We did not understand and just rewrote the same thing on Golang - after that everything worked.


So how does it work? We use the Firebase REST API to keep track of the changes of the branches of interest to us, and in the case of adding new elements we send a push through FCM. Where does it work? Yes, anywhere. And this is one of the main advantages. You do not need to have a static IP and decent hosting (but this directly depends on the number of guns sent).

')

But before you get down to business, you need to understand two things.



First, tracking the entire database will require it to be preloaded. And if the “server helper” was lying (or moved to another computer), then it will download everything again and resend the pushes. To solve this problem, I created a notif branch at the root of the database - users (or the content uploader) add notifications to it, which need to be sent to users, and the server deletes them after sending. I use this structure:



"notif" { "$key" { //    push()  "from": "2vgajTP5Vd...", // UID  "to": "all_users", //   ,  UID "value": "Hello, Habr!", //  ,     "type": "message"//  ,      } } 


Secondly, we need to know where to send. Therefore, I created another “tokens” branch in which devices write registration tokens to FCM. Subtleties of implementation on client devices is already a topic for a separate article. I keep them in this thread in the format:



 "tokens" { "userId": "fcmToken" } 


Also, so that the message could not be sent on behalf of another person or received by others, I added the Firebase Database Rules:



 { "rules": { ///     "notif": { ".read": "false", "$key": { ".write": "auth != null && newData.child('from').val() === auth.uid" } }, "tokens": { ".read": "false", "$key": { ".write": "auth != null && $key == auth.uid" } } } } 


We also need keys and libraries:





Implementation (well, finally!)



To simplify the example, I removed the caching of tokens from it, deleting obsolete and checking the purchases of the application through the Android publisher API, but if you are interested in any of this, write in the comments, share the full code.



So, the main part of the program:



 package main import ( "github.com/zabawaba99/firego" "github.com/edganiukov/fcm" "fmt" "log" ) const ( //TODO     FDBSecret = "P3cUiIQytto**************NzQM5TrzERjEDO" FCMAPIKey = "AIzaSyDXjRG**************8oOCMrPj18JVD8" DAY_IN_SEC = 86400 //     TOKENS = "tokens" NOTIFICATIONS = "notif" ) var ( FBDB = firego.New("https://kidgl.firebaseio.com", nil) //       FCM, _ = fcm.NewClient(FCMAPIKey) //     ) func main() { FBDB.Auth(FDBSecret) FBDB.Child(NOTIFICATIONS).ChildAdded(gotPush) // ,  ,  for { var res string fmt.Scanln(&res) if res == "exit" { return } else { println(`Type "exit" to stop service`) } } } 


The ChildAdded function accepts as input a function that it will call in case of changes in the database. This is all done in a separate thread (and maybe not in one, how do I know), the so-called Goroutine. Therefore, so that the program does not end, I use an eternal loop (and it will end anyway from some exception, restarting is done by a bash script that takes stderr as input).



With this, everything is clear, now the gotPush function:



 func gotPush(snapshot firego.DataSnapshot, previousChildKey string) { //    ,       FBDB.Child(NOTIFICATIONS).Child(snapshot.Key).Remove() //     data := snapshot.Value.(map[string]string{}) from := data["from"] to := data["to"] typ := data["type"] //   ,      ,     var token string FBDB.Child(TOKENS).Child(to).Value(&token) msg := &fcm.Message{ Token: token, // Data -  ,       Data: &fcm.Data{ "from": from, "type": typ, "value": data["value"], }, CollapseKey: typ + from + to, //       Priority: "high", ContentAvailable: true, TimeToLive: DAY_IN_SEC, //        } response, err := FCM.Send(msg) if (err!=nil) { log.Println(err) } println(": ", response.Success) println(": ", response.Failure) if response.Results[0].Unregistered() { // TODO:    ,            } } 


Well, in general, that's all, you can run and enjoy life to push. In my case, it was still necessary to compile for linux on the macbook, I think many people will also need the help of `env GOOS = linux GOARCH = amd64 go build backend_helper.go`



Thanks for reading!

Source: https://habr.com/ru/post/314350/



All Articles