5

I have an app that uses Firebase to send and receive messages between users. During the process of updating my code to the new Swift 3 so that I can add the cool new features of iOS 10 to my app, I ran into several errors. I was able to fix the majority of them except this one at runtime:

Terminating app due to uncaught exception 'InvalidPathValidation', reason: '(child:) Must be a non-empty string and not contain '.' '#' '$' '[' or ']''

I have no clue what could have caused this. The user's userID gets printed to the console when they are logged in, but yet my app crashes as soon as the log in button is pressed. This error was non-existent before I updated to Swift 3, and actually only appeared after I fixed a bug in another part of my application.

Anyways, I only have two main classes in my application: LoginViewController and ChatViewController.

Here is the code for LoginViewController:

import UIKit
import Firebase

class LoginViewController: UIViewController {

    // MARK: Properties
    var ref: FIRDatabaseReference! // 1
    var userID: String = ""

    override func viewDidLoad() {
        super.viewDidLoad()
        ref = FIRDatabase.database().reference() // 2
    }

    @IBAction func loginDidTouch(_ sender: AnyObject) {
        FIRAuth.auth()?.signInAnonymously() { (user, error) in
            if let user = user {
                print("User is signed in with uid: ", user.uid)
                self.userID = user.uid
            } else {
                print("No user is signed in.")
            }

            self.performSegue(withIdentifier: "LoginToChat", sender: nil)

        }

    }

    override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        super.prepare(for: segue, sender: sender)
        let navVc = segue.destinationViewController as! UINavigationController // 1
        let chatVc = navVc.viewControllers.first as! ChatViewController // 2
        chatVc.senderId = userID // 3
        chatVc.senderDisplayName = "" // 4
    }


}

Here is my code for ChatViewController:

import UIKit
import Firebase
import JSQMessagesViewController

class ChatViewController: JSQMessagesViewController {

    // MARK: Properties

    var rootRef = FIRDatabase.database().reference()
    var messageRef: FIRDatabaseReference!

    var messages = [JSQMessage]()
    var outgoingBubbleImageView: JSQMessagesBubbleImage!
    var incomingBubbleImageView: JSQMessagesBubbleImage!

    var userIsTypingRef: FIRDatabaseReference! // 1
    private var localTyping = false // 2
    var isTyping: Bool {
        get {
            return localTyping
        }
        set {
            // 3
            localTyping = newValue
            userIsTypingRef.setValue(newValue)
        }
    }
    var usersTypingQuery: FIRDatabaseQuery!


    override func viewDidLoad() {
        super.viewDidLoad()
        title = "ChatChat"
        setupBubbles()
        // No avatars
        collectionView!.collectionViewLayout.incomingAvatarViewSize = CGSize.zero
        collectionView!.collectionViewLayout.outgoingAvatarViewSize = CGSize.zero
        messageRef = rootRef.child("messages")
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        observeMessages()
        observeTyping()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
    }


    func collectionView(collectionView: JSQMessagesCollectionView!,
                                 messageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageData! {
        return messages[indexPath.item]
    }

    func collectionView(collectionView: JSQMessagesCollectionView!,
                                 messageBubbleImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageBubbleImageDataSource! {
        let message = messages[indexPath.item] // 1
        if message.senderId == senderId { // 2
            return outgoingBubbleImageView
        } else { // 3
            return incomingBubbleImageView
        }
    }

    override func collectionView(_ collectionView: UICollectionView,
                                 numberOfItemsInSection section: Int) -> Int {
        return messages.count
    }

    func collectionView(collectionView: JSQMessagesCollectionView!,
                                 avatarImageDataForItemAtIndexPath indexPath: NSIndexPath!) -> JSQMessageAvatarImageDataSource! {
        return nil
    }

    private func setupBubbles() {
        let factory = JSQMessagesBubbleImageFactory()
        outgoingBubbleImageView = factory?.outgoingMessagesBubbleImage(
            with: UIColor.jsq_messageBubbleBlue())
        incomingBubbleImageView = factory?.incomingMessagesBubbleImage(
            with: UIColor.jsq_messageBubbleLightGray())
    }

    func addMessage(id: String, text: String) {
        let message = JSQMessage(senderId: id, displayName: "", text: text)
        messages.append(message!)
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
        let cell = super.collectionView(collectionView, cellForItemAt: indexPath) as! JSQMessagesCollectionViewCell

        let message = messages[indexPath.item]

        if message.senderId == senderId {
            cell.textView!.textColor = UIColor.white()
        } else {
            cell.textView!.textColor = UIColor.black()
        }

        return cell
    }

    func didPressSendButton(button: UIButton!, withMessageText text: String!, senderId: String!,
                                     senderDisplayName: String!, date: NSDate!) {

        let itemRef = messageRef.childByAutoId() // 1
        let messageItem = [ // 2
            "text": text,
            "senderId": senderId
        ]
        itemRef.setValue(messageItem as? AnyObject) // 3

        // 4
        JSQSystemSoundPlayer.jsq_playMessageSentSound()

        // 5
        finishSendingMessage()

        isTyping = false

    }

    private func observeMessages() {
        // 1
        let messagesQuery = messageRef.queryLimited(toLast: 25)
        // 2
        messagesQuery.observe(.childAdded) { (snapshot: FIRDataSnapshot!) in
            // 3
            let id = snapshot.value!["senderId"] as! String
            let text = snapshot.value!["text"] as! String

            // 4
            self.addMessage(id: id, text: text)

            // 5
            self.finishReceivingMessage()
        }
    }

    private func observeTyping() {
        let typingIndicatorRef = rootRef.child("typingIndicator")
        userIsTypingRef = typingIndicatorRef.child(senderId)
        userIsTypingRef.onDisconnectRemoveValue()

        // 1
        usersTypingQuery = typingIndicatorRef.queryOrderedByValue().queryEqual(toValue: true)

        // 2
        usersTypingQuery.observe(.value) { (data: FIRDataSnapshot!) in

            // 3 You're the only typing, don't show the indicator
            if data.childrenCount == 1 && self.isTyping {
                return
            }

            // 4 Are there others typing?
            self.showTypingIndicator = data.childrenCount > 0
            self.scrollToBottom(animated: true)
        }
    }

    override func textViewDidChange(_ textView: UITextView) {
        super.textViewDidChange(textView)
        // If the text is not empty, the user is typing
        isTyping = textView.text != ""
    }

    func collectionView(collectionView: JSQMessagesCollectionView!, attributedTextForCellBottomLabelAtIndexPath indexPath: NSIndexPath!) -> AttributedString! {
        return AttributedString(string:"test")
    }

}

Mind, all of this code is written in Swift 3.

If you can find anything that will help me fix my problem and finally get my app to work, I will be your best friend.

Thanks in advance!

Ahad Sheriff
  • 1,829
  • 8
  • 24
  • 46

2 Answers2

3

Swift 3.0 is a developer release that is beta currently, and is expected to be released in late 2016.

Some libraries may not be available as of yet, or may contain bugs, so you should immediately reinstall the latest public stable version of Swift (currently as of writing v2.2).

You yourself said that you updated to a beta version of Xcode. Therefore, I advise you to reinstall Swift 2.2 by re-downloading Xcode from the Mac App Store and deleting the beta version of Xcode.

For Swift 3, wait until Apple releases the public version in Fall 2016. Then, see if your code works.


Firebase may not have updated its libraries for Swift 3.0 yet.

Until it does, I advise you continue using Swift 2.2. You wouldn't want your app (s) to suddenly stop working due to a compatibility error.

You said that you downloaded a beta version of Xcode. I would at least wait for the public version of Xcode to be released by Apple in Fall 2016 and then try your code again.

However, we don't know when Firebase will update its libraries. Keep a backup of your code, and download an updated version of Xcode in a spare machine. If your code works properly in your spare machine, download it in your main machine. Or, install a virtual machine such as VirtualBox and then try your app there.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Eddev
  • 890
  • 1
  • 10
  • 21
2

Swift 3

Firebase : If you create your own keys, they must be UTF-8 encoded, can be a maximum of 768 bytes, and cannot contain ., $, #, [, ], /, or ASCII control characters 0-31 or 127.

To deal with them you need to replace your non-firebase characters with something else.

Following String extension replaces those characters with space, you can choose your own. (Ex. '-')

Add:

extension String {
    func makeFirebaseString()->String{
        let arrCharacterToReplace = [".","#","$","[","]"]
        var finalString = self

        for character in arrCharacterToReplace{
            finalString = finalString.replacingOccurrences(of: character, with: " ")
        }

        return finalString
    }
}

Use:

let searchString = "Super.Hero"
let firebaseSearchString = searchString.makeFirebaseString()
// firebaseSearchString is "Super Hero" now
Mohammad Zaid Pathan
  • 16,304
  • 7
  • 99
  • 130