title: Firebase events order being changed by pagination function?
body: <p>I want to accomplish Pagination</p>
  
  <pre><code>import Foundation
  import UIKit
  import Alamofire
  import AlamofireNetworkActivityIndicator
  import SwiftLocation
  import CoreLocation
  
  class HomeFeedController: UICollectionViewController, UICollectionViewDelegateFlowLayout,UIGestureRecognizerDelegate {
      var isFinishedPaging = false
      let detailView = EventDetailViewController()
      let refreshControl = UIRefreshControl()
      var emptyLabel: UILabel?
      var allEvents = [Event]()
      //will containt array of event keys
      var eventKeys = [String]()
  
      var grideLayout = GridLayout(numberOfColumns: 2)
  
      let paginationHelper = PaginationHelper&lt;Event&gt;(serviceMethod: PostService.showEvent)
  
      override func viewDidLoad() {
          super.viewDidLoad()
          // we had to do this way because View Controller only has a view now Uicollection view does as well because it is a subclass of UI viewcontroller and because this controller also contains a collection view that is a child of the view controllers view we have to change the collection views background color because that's whats being presented to the screen
          collectionView?.backgroundColor = UIColor.white
          // may need to take this out
          //collectionView?.delegate = self
          //collectionView?.dataSource = self
          collectionView?.collectionViewLayout = grideLayout
          collectionView?.reloadData()
          self.collectionView?.contentInset = UIEdgeInsetsMake(20, 0, 0, 0)
          navigationItem.title = "Home Page"
          collectionView?.register(CustomCell.self, forCellWithReuseIdentifier: customCellIdentifier)
  
  //        PostService.showEvent(location: User.current.location!) { (event) in
  //            self.allEvents = event
  //            print(self.allEvents)
  //            
  //            DispatchQueue.main.async {
  //                self.collectionView?.reloadData()
  //            }
  //        }
          configureCollectionView()
          reloadHomeFeed()
      }
  
  
      func reloadHomeFeed() {
          self.paginationHelper.reloadData(completion: { [unowned self] (events) in
              self.allEvents = events
  
              if self.refreshControl.isRefreshing {
                  self.refreshControl.endRefreshing()
              }
  
              DispatchQueue.main.async {
                  self.collectionView?.reloadData()
              }
          })
      }
  
  
      func configureCollectionView() {
          // add pull to refresh
          refreshControl.addTarget(self, action: #selector(reloadHomeFeed), for: .valueChanged)
          collectionView?.addSubview(refreshControl)
      }
  
      // need to tell it how many cells to have
  
      override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -&gt; Int {
              return allEvents.count
      }
  
      let customCellIdentifier = "customCellIdentifier"
      // need to tell the collection view controller what type of cell we want to return
      override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -&gt; UICollectionViewCell {
  
          let customCell = collectionView.dequeueReusableCell(withReuseIdentifier: customCellIdentifier, for: indexPath) as! CustomCell
          let imageURL = URL(string: allEvents[indexPath.item].currentEventImage)
          print(imageURL ?? "")
          customCell.sampleImage.af_setImage(withURL: imageURL!)
          return customCell
      }
      override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
          //let selectedEvent = self.imageArray[indexPath.row]
          //let eventDetailVC
          if let cell = collectionView.cellForItem(at: indexPath){
              detailView.eventImage = allEvents[indexPath.row].currentEventImage
              detailView.eventName = allEvents[indexPath.row].currentEventName
            //  print("Look here for event name")
             // print(detailView.eventName)
              detailView.eventDescription = allEvents[indexPath.row].currentEventDescription
              detailView.eventStreet = allEvents[indexPath.row].currentEventStreetAddress
              detailView.eventCity = allEvents[indexPath.row].currentEventCity
              detailView.eventState = allEvents[indexPath.row].currentEventState
              detailView.eventZip = allEvents[indexPath.row].currentEventZip
              detailView.eventKey = allEvents[indexPath.row].key!
              detailView.eventPromo = allEvents[indexPath.row].currentEventPromo!
              detailView.eventDate = allEvents[indexPath.row].currentEventDate!
              detailView.eventTime = allEvents[indexPath.row].currentEventTime!
              detailView.currentEvent = allEvents[indexPath.row]
              present(detailView, animated: true, completion: nil)
              //self.navigationController?.pushViewController(detailView, animated: true)
  
          }
           print("Cell \(indexPath.row) selected")
      }
  
      override func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
  
          if indexPath.item &gt;= allEvents.count - 1 {
              print("paginating for post")
              paginationHelper.paginate(completion: { [unowned self] (events) in
                  self.allEvents.append(contentsOf: events)
  
                  DispatchQueue.main.async {
                      self.collectionView?.reloadData()
                  }
              })
          }else{
              print("Not paginating")
          }
      }
  
  
      func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -&gt; Bool {
          return true
      }
  
      //will make surepictures keep same orientation even if you flip screen
      // will most likely look into portrait mode but still good to have
      override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
          super.viewWillTransition(to: size, with: coordinator)
          grideLayout.invalidateLayout()
      }
      //Will allow the first two cells that are displayed to be of varying widths
      func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -&gt; CGSize {
          if indexPath.item == 0 || indexPath.item == 1 {
              return CGSize(width: view.frame.width, height: grideLayout.itemSize.height)
          }else{
              return grideLayout.itemSize
          }
      }
  
  
  }
  
  
  //responsible for populating each cell with content
  
  class CustomCell: UICollectionViewCell {
      override init(frame: CGRect) {
          super.init(frame: frame)
          setupViews()
      }
      let sampleImage: UIImageView = {
          let firstImage = UIImageView()
          firstImage.clipsToBounds = true
          firstImage.translatesAutoresizingMaskIntoConstraints = false
          firstImage.contentMode = .scaleToFill
          firstImage.layer.masksToBounds = true
          return firstImage
      }()
      let nameLabel: UILabel = {
          let name = UILabel()
          name.text = "Custom Text"
          name.translatesAutoresizingMaskIntoConstraints = false
          return name
      }()
      func setupViews() {
          addSubview(sampleImage)
          backgroundColor = UIColor.white
          //addSubview(nameLabel)
          addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": sampleImage]))
          addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[v0]|", options: NSLayoutFormatOptions(), metrics: nil, views: ["v0": sampleImage]))
      }
      required init?(coder aDecoder: NSCoder) {
          fatalError("init(coder:) has not been implemented")
      }
  
  }
  
  // responsible for creating the grid layout that you see in the home view feed
  
  class GridLayout: UICollectionViewFlowLayout {
  
      var numberOfColumns:Int = 2
  
      init(numberOfColumns: Int) {
          super.init()
          // controlls spacing inbetween them as well as spacing below them to next item
          self.numberOfColumns = numberOfColumns
          self.minimumInteritemSpacing = 3
          self.minimumLineSpacing = 5
      }
      // just needs to be here because swift tells us to
      required init?(coder aDecoder: NSCoder) {
          fatalError("init(coder:) has not been implemented")
      }
      override var itemSize: CGSize{
          get{
              if collectionView != nil {
                  let collectionVieweWidth = collectionView?.frame.width
                  let itemWidth = (collectionVieweWidth!/CGFloat(self.numberOfColumns)) - self.minimumInteritemSpacing
                  let itemHeight: CGFloat = 200
                  return CGSize(width: itemWidth, height: itemHeight)
              }
              return CGSize(width: 100, height: 100)
          }set{
              super.itemSize = newValue
          }
      }
  
  
  }
  </code></pre>
  
  <p>The pagination takes place thanks to this function listed below</p>
  
  <pre><code>import Foundation
  
  protocol Keyed {
      var key: String? { get set }
  }
  
  // Create a new instance using a genetic type
  //let paginationHelper = MGPaginationHelper&lt;Post&gt;()
  
  // Generic class type
  
  
  
  //   1. initial - no data has been loaded yet
  //   2.  ready - ready and waiting for next request to paginate and load the next page
  //   3.  loading - currently paginating and waiting for data from Firebase
  //   4.  end - all data has been paginated
  
  enum PaginationState
  {
      case initial
      case ready
      case loading
      case end
  }
  
  class PaginationHelper&lt;T : Keyed&gt;
  {
  
  
  
      // MARK: - Properties
  
      // 1. page size - Determines the number of posts that will be on each page
      // 2. serviceMethod - The service method that will return paginated data
      // 3. state - The current pagination state of the helper
      // 4. lastobjectKey - Firebase uses object keys to determine the last position of the page. We'lll need to use this as an offset for paginating.
      let pageSize: UInt
      let serviceMethod: (UInt, String?, @escaping (([T]) -&gt; Void)) -&gt; Void
      var state: PaginationState = .initial
      var lastObjectKey: String?
  
      // MARK: - Init
      //    Can change the default page size for our helper
      //    Set the service method that will be paginated and return data
      init(pageSize: UInt = 5, serviceMethod: @escaping (UInt, String?, @escaping (([T]) -&gt; Void)) -&gt; Void) {
          self.pageSize = pageSize
          self.serviceMethod = serviceMethod
      }
  
  
      // 1 Notice our completion parameter type. We use our generic type to enforce that we return type T.
      func paginate(completion: @escaping([T]) -&gt; Void)
      {
          // 2 We switch on our helper's state to determine the behavior of our helper when paginate(completion:) is called
          switch state
          {
          // 3 For our initial state, we make sure that the lastObjectKey is nil use the fallthrough keyword to execute the ready case below.
          case .initial:
              lastObjectKey = nil
              fallthrough
          //4 For our ready state, we make sure to change the state to loading and execute our service method to return the paginated data.
          case .ready:
              state = .loading
              print(lastObjectKey)
              serviceMethod(pageSize, lastObjectKey) { [unowned self] (objects: [T]) in
                  //5 We use the defer keyword to make sure the following code is executed whenever the closure returns. This is helpful for removing duplicate code.
                  defer {
                      //6 If the returned last returned object has a key value, we store that in lastObjectKey to use as a future offset for paginating. Right now the compiler will throw an error because it cannot infer that T has a property of key. We'll fix that next.
                      if let lastObjectKey = objects.first?.key {
                          self.lastObjectKey = lastObjectKey
                          print(self.lastObjectKey)
                          print(lastObjectKey)
                      }
                      // 7 We determine if we've paginated through all content because if the number of objects returned is less than the page size, we know that we're only the last page of objects.
                      self.state = objects.count &lt; Int(self.pageSize) ? .end : .ready
                  }
  
                  // 8 If lastObjectKey of the helper doesn't exist, we know that it's the first page of data so we return the data as is.
                  guard let _ = self.lastObjectKey else {
                      print(self.lastObjectKey)
                      return completion(objects)
                  }
  
                  // 9 Due to implementation details of Firebase, whenever we page with the lastObjectKey, the previous object from the last page is returned. Here we need to drop the first object which will be a duplicate post in our timeline. This happens whenever we're no longer on the first page.
                  print(objects.last?.key)
                  let newObjects = Array(objects.dropLast())
                //  print(newObjects)
                  completion(newObjects)
  
              }
  
          //10 If the helper is currently paginating or has no more content, the helper returns and doesn't do anything.
          case . loading, .end:
              return
          }
      }
  
      //  resets the pagination helper to it's initial state
      func reloadData(completion: @escaping ([T]) -&gt; Void)
      {
          state = .initial
          paginate(completion: completion)
      }
  
  
  }
  </code></pre>
  
  <p>The Service Method that this pagination uses to operate is this</p>
  
  <pre><code>static func showEvent(pageSize: UInt, lastPostKey: String? = nil,completion: @escaping ([Event]) -&gt; Void) {
          //getting firebase root directory
          print(lastPostKey)
          var currentEvents = [Event]()
          let eventsByLocationRef = Database.database().reference().child("eventsbylocation").child(User.current.location!)
  
          //let ref = Database.database().reference().child("events")
          var query = eventsByLocationRef.queryOrderedByKey().queryLimited(toFirst: pageSize)
          if let lastPostKey = lastPostKey {
              print(lastPostKey)
              query = query.queryEnding(atValue: lastPostKey)
          }
          query.observeSingleEvent(of: .value, with: { (snapshot) in
              print(snapshot)
              print(snapshot.value)
              guard let allObjects = snapshot.children.allObjects as? [DataSnapshot] else{
                  return
              }
  
              allObjects.forEach({ (snapshot) in
                print(snapshot.value)
                  EventService.show(forEventKey: snapshot.value as! String, completion: { (event) in
                      currentEvents.append(.init(currentEventKey: snapshot.value as! String, dictionary: (event?.eventDictionary)!))
                    //  print(currentEvents)
                      print(currentEvents.count)
                      completion(currentEvents)
                  })
  
              })
  
  
          })
  
      }
  </code></pre>
  
  <p>okay now that I have provided the methods I can explain the problem. 
  When I print out my snapshot for my showEvent function I get this order</p>
  
  <pre><code>Snap (37%2e7,-122%2e4) {
      event = MIA;
      event2 = CCDS;
      event3 = MIA2;
      event4 = MIA3;
      event5 = CCDS;
  }
  </code></pre>
  
  <p>This is a snapshot printed based off of my database which looks like this</p>
  
  <pre><code>  "events" : {
      "CCDS" : {
        "attend:count" : 2,
        "event:city" : "San Francisco",
        "event:date" : {
          "end:date" : "08/09/2017",
          "end:time" : "7:00 PM",
          "start:date" : "08/09/2017",
          "start:time" : "5:00 PM"
        },
        "event:description" : "Happy hour is more joyful in the summer thanks to Center City District Sips, which offers discounted drinks and appetizers every Wednesday evening.  Catch up with old friends and make a few new ones as Center City’s best bars and restaurants host the summer’s happiest hour every Wednesday from 5-7 p.m.  Enjoy $5 cocktails, $4 wine, $3 beers and half-price appetizers at dozens and dozens of bars and restaurants.",
        "event:imageURL" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/event_flyers%2FCCDS-compressor.jpg?alt=media&amp;token=bcce3968-1cca-4890-a3d0-d8064bd0d1da",
        "event:name" : "center city district sips",
        "event:promo" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/event_promo_vid%2FMIA%2FBudweiser%20Made%20In%20America%20Festival%202013%20August%2031%20-%20September%201.mp4?alt=media&amp;token=9b4b9d4f-2d6d-4762-a5fd-c72edb943ac4",
        "event:state" : "PA",
        "event:street:address" : "660 Chestnut St",
        "event:zip" : 19130
      },
      "DD" : {
        "attend:count" : 2,
        "event:city" : "New York",
        "event:date" : {
          "end:date" : "08/26/2017",
          "end:time" : "5:00 PM",
          "start:date" : "08/26/2017",
          "start:time" : "1:00 PM"
        },
        "event:description" : "Help us celebrate the hard work and creativity of the students and demo the iOS apps and games they've built in only 8 weeks! Developers, entrepreneurs, friends, and industry professionals are all welcome to attend",
        "event:imageURL" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/event_flyers%2Fmakeschooldemo-compressor.jpg?alt=media&amp;token=1e75d18d-1949-48e8-a208-2ca88cde395b",
        "event:name" : "demo day",
        "event:promo" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/event_promo_vid%2FDD%2FDemo%20Night%202017%20-%20Make%20School%20Product%20College.mp4?alt=media&amp;token=d9adb4b1-5689-4b15-96b2-e18138701526",
        "event:state" : "PA",
        "event:street:address" : "394 Broadway",
        "event:zip" : 10013
      },
      "MIA" : {
        "attend:count" : 23,
        "event:city" : "San Francisco",
        "event:date" : {
          "end:date" : "09/03/2017",
          "end:time" : "7:00 PM",
          "start:date" : "09/02/2017",
          "start:time" : "12:00 PM"
        },
        "event:description" : "Budweiser Made in America Festival is an annual music festival held in Philadelphia and formerly simultaneously held in Los Angeles.Sponsored by Anheuser–Busch and produced by Live Nation, the event features several stages that continuously host live music from a wide range of genres including hip hop, rock, pop, R&amp;B, and EDM.",
        "event:imageURL" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/event_flyers%2FMadeInAmerica-compressor.jpg?alt=media&amp;token=1ac6e794-6a1f-4f8a-bdb0-afc91f8ba6ae",
        "event:name" : "made in america",
        "event:promo" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/event_promo_vid%2FMIA%2FBudweiser%20Made%20In%20America%20Festival%202013%20August%2031%20-%20September%201.mp4?alt=media&amp;token=9b4b9d4f-2d6d-4762-a5fd-c72edb943ac4",
        "event:state" : "PA",
        "event:street:address" : "Ben Franklin Parkway",
        "event:zip" : 19130
      },
      "MIA2" : {
        "attend:count" : 2,
        "event:city" : "Philadelphia",
        "event:date" : {
          "end:date" : "09/03/2017",
          "end:time" : "7:00 PM",
          "start:date" : "09/02/2017",
          "start:time" : "12:00 PM"
        },
        "event:description" : "Budweiser Made in America Festival is an annual music festival held in Philadelphia and formerly simultaneously held in Los Angeles. Sponsored by Anheuser–Busch and produced by Live Nation, the event features several stages that continuously host live music from a wide range of genres including hip hop, rock, pop, R&amp;B, and EDM. This is different regardless of the same name in database",
        "event:imageURL" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/event_flyers%2Fmadeinamerica2.jpg?alt=media&amp;token=bd5eb5e4-4e22-4412-a34b-2dc6c9ae561e",
        "event:name" : "made in america",
        "event:promo" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/event_promo_vid%2FMIA%2FBudweiser%20Made%20In%20America%20Festival%202013%20August%2031%20-%20September%201.mp4?alt=media&amp;token=9b4b9d4f-2d6d-4762-a5fd-c72edb943ac4",
        "event:state" : "PA",
        "event:street:address" : "Ben Franklin Parkway",
        "event:zip" : 19130
      },
      "MIA3" : {
        "attend:count" : 8,
        "event:city" : "Philadelphia",
        "event:date" : {
          "end:date" : "09/03/2017",
          "end:time" : "7:00 PM",
          "start:date" : "09/02/2017",
          "start:time" : "12:00 PM"
        },
        "event:description" : "Budweiser Made in America Festival is an annual music festival held in Philadelphia and formerly simultaneously held in Los Angeles. Sponsored by Anheuser–Busch and produced by Live Nation, the event features several stages that continuously host live music from a wide range of genres including hip hop, rock, pop, R&amp;B, and EDM.",
        "event:imageURL" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/event_flyers%2Fmadeinamerica3-compressor.jpg?alt=media&amp;token=9166e2fb-ac9d-46d5-b06f-60cffc337b15",
        "event:name" : "made in america",
        "event:promo" : "https://firebasestorage.googleapis.com/v0/b/eventful-3d558.appspot.com/o/event_promo_vid%2FMIA%2FBudweiser%20Made%20In%20America%20Festival%202013%20August%2031%20-%20September%201.mp4?alt=media&amp;token=9b4b9d4f-2d6d-4762-a5fd-c72edb943ac4",
        "event:state" : "PA",
        "event:street:address" : "Ben Franklin Parkway",
        "event:zip" : 19130
      }
    },
    "eventsbylocation" : {
      "37%2e7,-122%2e4" : {
        "event0" : "MIA",
        "event1" : "CCDS",
        "event2" : "MIA2",
        "event3" : "MIA3",
        "event4" : "CCDS",
        "event5" : "MIA",
        "event6" : "MIA2",
        "event7" : "MIA3",
        "event8" : "CCDS",
        "event9" : "MIA"
      },
      "40%2e7,-74%2e0" : {
        "event" : "DD"
      }
    },
  </code></pre>
  
  <p>Based off of the function it should pass in each of these in the same order and return them in the same order. I accomplish this using a foreach loop where I pass in the snapshot.value which would MIA for the first snapshot</p>
  
  <pre><code> Snap (37%2e7,-122%2e4) {
          event = MIA;
          event2 = CCDS;
          event3 = MIA2;
          event4 = MIA3;
          event5 = CCDS;
      }
  </code></pre>
  
  <p>They get passed into my showEvent function in the proper order but when the events are returned by the completion handler it comes back in the order</p>
  
  <pre><code>MIA
  CCDS
  CCDS
  MIA2
  MIA3
  </code></pre>
  
  <p>It then drops off the last one. Now this is causing a lot of errors because
  1. I passed them in in this order</p>
  
  <pre><code>MIA
  CCDS
  MIA2
  MIA3
  CCDS
  </code></pre>
  
  <ol start="2">
  <li>When I try to paginate for the next 5 the last object key is set to MIA3 which is causing my database call to return null which is preventing my pagination process.</li>
  </ol>
  
  <p>So in short my question is
  2
  Why is it returning in a different order and how can I make sure it returns in the same order that I passed it in so my pagination can work properly?</p>