Upvote/Downvote system within Swift via Firebase

后端 未结 3 1422
天命终不由人
天命终不由人 2020-12-05 03:34

I\'ve looked over hours of code and notes and I\'m struggling to find any documentation that would help me with upvoting and downvoting an object in a swift app with firebas

相关标签:
3条回答
  • 2020-12-05 04:09

    I will describe how I implemented such a feature in social networking app Impether using Swift and Firebase.

    Since upvoting and downvoting is analogous, I will describe upvoting only.

    The general idea is to store a upvotes counter directly in the node corresponding to an image data the counter is related to and update the counter value using transactional writes in order to avoid inconsistencies in the data.

    For example, let's assume that you store a single image data at path /images/$imageId/, where $imageId is an unique id used to identify a particular image - it can be generated for example by a function childByAutoId included in Firebase for iOS. Then an object corresponding to a single photo at that node looks like:

    $imageId: {
       'url': 'http://static.example.com/images/$imageId.jpg',
       'caption': 'Some caption',
       'author_username': 'foobarbaz'
    }
    

    What we want to do is to add an upvote counter to this node, so it becomes:

    $imageId: {
       'url': 'http://static.example.com/images/$imageId.jpg',
       'caption': 'Some caption',
       'author_username': 'foobarbaz',
       'upvotes': 12,
    }
    

    When you are creating a new image (probably when an user uploads it), then you may want to initialize the upvote counter value with 0 or some other constant depending on what are you want to achieve.

    When it comes to updating a particular upvotes counter, you want to use transactions in order to avoid inconsistencies in its value (this can occur when multiple clients want to update a counter at the same time).

    Fortunately, handling transactional writes in Firebase and Swift is super easy:

    func upvote(imageId: String,
                success successBlock: (Int) -> Void,
                error errorBlock: () -> Void) {
    
        let ref = Firebase(url: "https://YOUR-FIREBASE-URL.firebaseio.com/images")
            .childByAppendingPath(imageId)
            .childByAppendingPath("upvotes")
    
        ref.runTransactionBlock({
            (currentData: FMutableData!) in
    
            //value of the counter before an update
            var value = currentData.value as? Int
    
            //checking for nil data is very important when using
            //transactional writes
            if value == nil {
                value = 0
            }
    
            //actual update
            currentData.value = value! + 1
            return FTransactionResult.successWithValue(currentData)
            }, andCompletionBlock: {
                error, commited, snap in
    
                //if the transaction was commited, i.e. the data
                //under snap variable has the value of the counter after
                //updates are done
                if commited {
                    let upvotes = snap.value as! Int
                    //call success callback function if you want
                    successBlock(upvotes)
                } else {
                    //call error callback function if you want
                    errorBlock()
                }
        })
    }
    

    The above snipped is actually almost exactly the code we use in production. I hope it helps you :)

    0 讨论(0)
  • 2020-12-05 04:11

    Not a Swift fella myself (pun!) but I think this stackoverflow question has most of your answers.

    Then you would simply use a couple of if statements to return the correct value from the transaction based on whether you want to up vote or down vote.

    0 讨论(0)
  • 2020-12-05 04:27

    I was very surprised, but this code from original docs works like a charm. There is one disadvantage with it: the json grows pretty big if there are a lot of likes.

    FirebaseService.shared.databaseReference
                .child("items")
                .child(itemID!)
                .runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
                    if var item =  currentData.value as? [String : AnyObject] {
                    let uid = SharedUser.current!.id
    
                    var usersLikedIdsArray = item["liked_who"] as? [String : Bool] ?? [:]
                    var likesCount = item["likes"] as? Int ?? 0
    
                        if usersLikedIdsArray[uid] == nil  {
                            likesCount += 1
                            usersLikedIdsArray[uid] = true
                            self.setImage(self.activeImage!, for: .normal)
                            self.updateClosure?(true)
                        } else {
                            likesCount -= 1
                            usersLikedIdsArray.removeValue(forKey: uid)
                            self.setImage(self.unactiveImage!, for: .normal)
                            self.updateClosure?(false)
                        }
    
                    item["liked_who"] = usersLikedIdsArray as AnyObject?
                    item["likes"] = likesCount as AnyObject?
    
                    currentData.value = item
    
                    return TransactionResult.success(withValue: currentData)
                }
                return TransactionResult.success(withValue: currentData)
            }) { (error, committed, snapshot) in
                if let error = error {
                    self.owner?.show(error: error)
                }
            }
    
    0 讨论(0)
提交回复
热议问题