1

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

{
  "Username" : {
    "Username" : {
      "email" : "[email protected]"
    }
  },
  "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!
}
26
  • Please don't use Arrays in Firebase. They are not queryable or editable (add/remove) and there are usually much better options for storing your data. See the answer to this question before you go down that path. This one as well. Commented Dec 6, 2017 at 18:05
  • @jay thank you for the input. The problem is that I hard coded the adding songs part of this app. I am not sure what direction to go in without the array. All I need is for the array to be saved to a user so that when that user signs in, they have their own favorite songs list. Commented Dec 6, 2017 at 19:27
  • Right. What if they want to change the order of the songs? You will have to read the songs, make the change, delete the node in firebase, re-write the songs. Suppose the user has 1000 songs and they want to query for a song - that can't be done with a firebase array. Suppose they want to insert a song at index #3 in the playlist. Can't do that either! Working with an array in code is best practice - just don't do it in Firebase. Let me put an answer together with some options. Commented Dec 6, 2017 at 21:40
  • 1
    @Jay thank you, you advice gave me a way better understanding of this. I was able to load your array into firebase, as well as retrieve it (displayed in console when I clicked the button). I can close the app and click the reload button (Will implement the reloadData later) and the array appears in my table view controller. Now I need to figure out how to implement this with my empty array that the user populates. Commented Dec 8, 2017 at 19:09
  • 1
    Move the list array inside the class definition. Add a .childAdded observer to that users playlist in Firebase. When the user adds a song, write it to that node. Adding the song to Firebase will then fire the observer and within that observer closure, a snapshot will be passed in containing the song data that was just added. Add the song to the array tableView dataSource, and reload your tableview. That's about it. If my answer helped, please accept it so it can help others! Commented Dec 8, 2017 at 19:13

2 Answers 2

9

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()
Sign up to request clarification or add additional context in comments.

2 Comments

appreciate the input immensely. Thank you for taking your time to respond! I figured out how to upload the array, and now am having trouble retrieving the data and sending it back to the app. I will change the structure once I figure out how to retrieve the data but for now I just want to get the app to work. My current issue is that when I close the app, none of the data is saved in the array which is obviously because I am not retrieving it from firebase. I have tried a multitude of things but have had no success. Here is what I have atm
@Justin I got ya! Updated my answer to also read the data back into an array. If this answer helped, be sure to accept it so it can help others.
6

Firebase isn't that bad with arrays. I assume this functionality is newer than this thread, but I'm posting here for future people like myself.

Here's the section in the Firebase documentation that says how to make changes to an array. It's not perfect, just another tool in the shed.

Quoted from their docs:

let washingtonRef = db.collection("cities").document("DC")

// Atomically add a new region to the "regions" array field.
washingtonRef.updateData([
    "regions": FieldValue.arrayUnion(["greater_virginia"])
])

// Atomically remove a region from the "regions" array field.
washingtonRef.updateData([
    "regions": FieldValue.arrayRemove(["east_coast"])
])

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.