Search code examples
iosswiftxcodefirebasexcode8

How to add an Array to firebase using Swift (Xcode)


Here is my JSON Structure (Ignore the username to email part of it, that is for a test)

{
  "Username" : {
    "Username" : {
      "email" : "test@gmail.com"
    }
  },
  "uid_0" : {
    "Song List" : [ "The National Anthem" ]
  }
}

I am making a very simplistic app using Swift and Xcode. Basically, the user signs into their account after they register. From there the user is able to choose their favorite songs from a band's discography into an empty array. The user is then able to click a button that segues them to a table view controller that contains the songs they added. The way the user adds songs is through a view controller that contains labels that have the song names and buttons next to each song name. When the user clicks the button the label is added to the array.

I want to be able to save the array to firebase so that each user will have their own favorite songs. I have done a decent amount of research and can not find anything to steer me in the right direction as this is incredibly simple. The labels have no keys and are literally just labels being added to an array.

I have the firebase storage and database pods installed as well and I am able to upload images to fire base and have the user save that image to their specific account but how to do the same thing for an array? Here is my code for the array so you can get an idea of how it works.

import UIKit
import Firebase
import FirebaseStorage
import FirebaseAuth
import LocalAuthentication
import FirebaseDatabase

//var refSongs: DatabaseReference! {
//  return Database.database().reference()

//}

var list = [String]()
class SongsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    var ref: DatabaseReference!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.ref = Database.database().reference()
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return list.count
    }

    @IBAction func saveSongs(_ sender: Any) {
        //upload array to firebase storage
        //let defaults = UserDefaults.standard.set(list, forKey: "KeySave")
        //addSongs()

        // create add list reference in fire database
        // let addListRef = refSongs.child("Song List").childByAutoId()

        // let addList1 = list

        // addListRef.setValue(addList1)

        let ref = self.ref.child("uid_0").child("Song List")
        // let songArray = [list] *** Removed this
        ref.setValue(list) //changed to list
        retriveList()
    }

    func retriveList() {
        let ref = self.ref.child("uid_0").child("Song List")
        ref.observeSingleEvent(of: .value, with: {snapshot in
            var mySongArray = [String]()
            for child in snapshot.children {
                let snap = child as! DataSnapshot
                let song = snap.value as! String //!! Program crashes here!!
                mySongArray.append(song)
            }
            print(mySongArray)
        })
    }

    // add labels to array
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "cell")
        cell.textLabel?.text = list[indexPath.row]
        return (cell)
    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == UITableViewCellEditingStyle.delete
        {
            list.remove(at: indexPath.row)
            myTableView.reloadData()
        }
    }

    override func viewDidAppear(_ animated: Bool) {
        myTableView.reloadData()
    }

    @IBOutlet weak var myTableView: UITableView!
}

Solution

  • Writing an array to Firebase is incredibly easy and Firebase does most of the work for you.

    Suppose the user selects three songs and now you want to store them in the users playlist

    let ref = self.ref.child("uid_0").child("playlist")
    let songArray = ["Us and Them", "Get Back", "Children of the Sun"]
    ref.setValue(songArray)
    

    will result in

    uid_0
      playlist
        0: "Us and Them"
        1: "Get Back"
        2: "Children of the Sun"
    

    Now the user selects another song to add to the playlist and you want to save it. Oh wait.. snap. Firebase treats the array as one object so you have to read the node, delete that node and re-write the node with the new data.

    Now suppose the user wants to query for the title of song #2. Can't do that either! Firebase Can't query inside an array.

    Ok, so you have 100 users and want to add a feature to show the users whose favorite song is Children of the Sun. Can't do that either.

    What if the user wants to have multiple playists?

    Basically Arrays Are Evil and limited and there are usually much better options for storing data.

    That being said, arrays can be useful in some cases - two options are shown here.

    Here's an example

    song_library
       song_0
         title: "Us and Them"
         artist: "Pink Floyd"
         play_count: 100
         total_likes: 167
       song_1
         title: "Get Back"
         artist: "Beatles"
         play_count: 50
         total_likes: 87
       song_2
         title: "Children of the Sun"
         artist: "Billy Thorpe"
         play_count: 98
         total_likes: 1050
       song_3
         title: "21st Century Man"
         artist: "Billy Thorpe"
         play_count: 36
         total_likes: 688
       song_4
         title: "East of Edens Gate"
         artist: "Billy Thorpe"
         play_count: 45
         total_likes: 927
    
    uid_0
       playlist_0
         song_1: 2
         song_2: 0
         song_4: 1
         //or use an array here
         //0: song_2
         //1: song_4
         //2: song_0
       playlist_1
         song_3: 0
         song_4: 1
    

    With this structure, you can re-use songs in playlists, the sequence can be easily modified, songs and be removed or added and data duplication is significantly reduced as the playlists just keep references to the actual song data.

    EDIT:

    To read that node back into an array, here's one option

    let ref = self.ref.child("uid_0").child("playlist")
    ref.observeSingleEvent(of: .value, with: { snapshot in
        var mySongArray = [String]()
        for child in snapshot.children {
            let snap = child as! DataSnapshot
            let song = snap.value as! String
            mySongArray.append(song)
        }
        print(mySongArray)
    })
    

    Note there are other options but this guarantees the order.

    Edit 2

    This is to help the OP define their class vars correctly.

    class ViewController: UIViewController {
    
        var ref: DatabaseReference!
    
        override func viewDidLoad() {
            super.viewDidLoad() 
            self.ref = Database.database().reference()