Algolia not indexing data inside iOS app unless I run npm start. The Node.js code isn't syncing with the app. Updated!

I have an iOS app with Firebase for my database. I’m using Algolia to index and it’s hosted on a Heroku server. When I send data to Firebase and I then press the searchButton inside my app to search the data I just sent, the search bar will not return anything. When I go to the Algolia console and look inside the Indices tab, it says “You don’t have any Index”. The thing is if I go to the cli and run: npm start, I see the Indices tab immediately index the data (results are visible) from Firebase and then if I press the searchButton inside my app the results show up. The other issue I have is if I update or delete the data from either inside my app or directly in firebase, when I again press the searchButton, the new data doesn’t appear but the deleted data still shows inside the tableView even though both changes are apparent in Firebase. But like I previously said, if I go to terminal and run: npm start, the deleted data is removed from the Indices tab, the new data is shown (with the existing data), and if I press the searchButton inside my app, the deleted data doesn’t appear, and the updated data does.

I had someone look at it and they said it can’t be an iOS issue because I wouldn’t be able to search for anything. The fact that I have to run npm start inside terminal to update the Index means that the syncing problem must be happening inside the Node.js code and that’s possibly why it’s not connecting with my iOS app (their words)???

For my Node.js code I followed the code from: https://www.algolia.com/doc/guides/integrations/firebase-algolia/. and this is what I currently use below (fyi the Firebase initialization code from the link is dated, I had to update to current FB requirements). This code currently sits inside my app.js file and it works because when I run npm start the Indices gets updated and my iOS can successfully run the searches.

//Firebase Initialization
var admin = require("firebase-admin");
admin.initializeApp({
  credential: admin.credential.cert({
    projectId: "...",
    clientEmail: "...",
    privateKey: ..."
  }),
  databaseURL: ..."
});
  var firebase = require('firebase');
  var config = {
      apiKey: "myFirebaseAPIKey",
      authDomain: "myFirbaseProjectID.firebaseapp.com",
      databaseURL: "myFB_URL",
      storageBucket: "myFirebaseStorageBucket"
    };
    firebase.initializeApp(config);
  //Algolia Initialization
  var algoliasearch = require('algoliasearch');
  var client = algoliasearch('myAlgoliaApplicationID', 'myAlgoliaPrivateAPIKey');
  var index = client.initIndex('meal');
  var rootRef = firebase.database().ref('meal');
  **// Import Existing Data**
  rootRef.on('value', initIndex);
  function initIndex(dataSnapshot) {
    // Array of data to index
    var objectsToIndex = [];
    // Get all objects
    var values = dataSnapshot.val();
    // Process each Firebase object
    for (var key in values) {
      if (values.hasOwnProperty(key)) {
        // Get current Firebase object
        var firebaseObject = values[key];
        // Specify Algolia's objectID using the Firebase object key
        firebaseObject.objectID = key;
        // Add object for indexing
        objectsToIndex.push(firebaseObject);
    }
    }
    // Add or update new objects
    index.saveObjects(objectsToIndex, function(err, content) {
      if (err) {
        throw err;
      }
      console.log('Firebase<>Algolia import done');
    });
  }
  **//Reindex Data**
  // Get all data from Firebase
  rootRef.on('value', reindexIndex);
  function reindexIndex(dataSnapshot) {
    // Array of objects to index
    var objectsToIndex = [];
      // Create a temp index
      var tempIndexName = 'meal_temp';
      var tempIndex = client.initIndex(tempIndexName);
      // Get all objects
      var values = dataSnapshot.val();
      // Process each Firebase object
      for (var key in values) {
        if (values.hasOwnProperty(key)) {
          // Get current Firebase object
          var firebaseObject = values[key];
          // Specify Algolia's objectID using the Firebase object key
          firebaseObject.objectID = key;
          // Add object for indexing
          objectsToIndex.push(firebaseObject);
        }
      }
      // Add or update new objects
      index.saveObjects(objectsToIndex, function(err, content) {
        if (err) {
          throw err;
        }
        // Overwrite main index with temp index
        client.moveIndex(tempIndexName, 'meal', function(err, content) {
          if (err) {
            throw err;
          }
          console.log('Firebase<>Algolia reimport done');
        });
      });
    }
  **//Add or Update Data**
  // Listen for changes to Firebase data
  rootRef.on('child_added', addOrUpdateObject);
  rootRef.on('child_changed', addOrUpdateObject);
  function addOrUpdateObject(dataSnapshot) {
    // Get Firebase object
    var firebaseObject = dataSnapshot.val();
    // Specify Algolia's objectID using the Firebase object key
    firebaseObject.objectID = dataSnapshot.key;
    // Add or update object
    index.saveObject(firebaseObject, function(err, content) {
      if (err) {
        throw err;
      }
      console.log('Firebase<>Algolia object saved');
    });
  }
  **//Delete Data**
  // Listen for changes to Firebase data
  rootRef.on('child_removed', removeIndex);
  function removeIndex(dataSnapshot) {
    // Get Algolia's objectID from the Firebase object key
    var objectID = dataSnapshot.key();
    // Remove the object from Algolia
    index.deleteObject(objectID, function(err, content) {
      if (err) {
        throw err;
      }
      console.log('Firebase<>Algolia object deleted');
    });
  }

The issue is why isn’t it updating/syncing live as soon as I press the searchButton?

One other thing I would an answer to is in the Node.js code from the Algolia link above, at the bottom of the both sections Import Existing Data and Reindex Data it says: Once you run this code, you will have all of your existing Firebase data indexed with Algolia. You will want to remove this code once it is done because the event will continue to fire each time data is added. I’m not a Node.js developer so I don’t know how to remove the code and since it says the same thing for both sections, should I not use 1 and use the other ? Or should I use them both but first run 1 then remove the code and then run the other and remove the code? Considering it says it for both sections with no context on how to remove the code, it’s not very clear.

This is the code to send data to Firebase:

  let mealID = NSUUID().UUIDString //this is apple's version of a random id generator similar to childByAutoID
 
  var mealDict = [String:AnyObject]()
  mealDict.updateValue("pizza", forKey: "food") 
  mealDict.updateValue("gingerAle", forKey: "drink"
  mealDict.updateValue("mealID", forKey: "mealID")
 
  let mealRef = self.dbRef.child("meal").child(mealID)    
  mealRef.updateChildValues(mealDict, withCompletionBlock: {
                          (error, ref) in
                          if error != nil{
                            print(error?.localizedDescription)
                            return
                  }
  })

For my iOS SearchResults code i followed everything from https://www.algolia.com/doc/guides/search/instant-search/swift/ (fyi I had to update a few things as some of the code was old):

import UIKit
import AlgoliaSearch
import SwiftyJSON

class SearchController: UIViewController{
@IBOutlet weak var tableView: UITableView!

var searchController: UISearchController!

var mealSearch = [MealModel]()

var mealIndex: Index!
let query = Query()
var searchId = 0
var displayedSearchId = -1
var loadedPage: UInt = 0
var nbPages: UInt = 0
override func viewDidLoad() {
    super.viewDidLoad()
    self.searchController attributes...
    let apiClient = Client(appID: "myAlgoliaAppID", apiKey: "myAlgoliaPublicAPIKey")
    self.mealIndex = apiClient.getIndex("meal")
    query.hitsPerPage = 15
    query.attributesToRetrieve = ["food", "drink"]
    
    self.tableView.reloadData()

func updateSearchResultsForSearchController(searchController: UISearchController) {
    
    query.query = searchController.searchBar.text
    let curSearchId = searchId
    
    self.mealIndex.search(self.query, completionHandler: {
        (data, error) in
        
        if curSearchId <= self.displayedSearchId || error != nil {
            print("problem #1")
            return // Newest query already displayed or error
        }
        
        // Decode JSON
        guard let hits = data!["hits"] as? [[String: AnyObject]] else { return }
        guard let nbPages = data!["nbPages"] as? UInt else { return }
        self.nbPages = nbPages
        
        var tmp = [MealModel]()
        for hit in hits {
            tmp.append(MealModel(json: hit))
        }
        //Reload view with the new data
        self.mealSearch = tmp
        self.tableView.reloadData()
    })
    self.searchId += 1
}

func loadMore() {
    if loadedPage + 1 >= nbPages {
        return // All pages already loaded
    }
    let nextQuery = Query(copy: query)
    nextQuery.page = loadedPage + 1
    mealIndex.search(nextQuery, completionHandler: {
        (data , error) in
        
        if nextQuery.query != self.query.query || error != nil {
            print("problem #2")
            return // Query has changed
        }
        
        self.loadedPage = nextQuery.page as! UInt
        let json = JSON(data!)
        let hits: [JSON] = json["hits"].arrayValue
        var tmp = [MealModel]()
        print("...\(hits.count)")
        for record in hits {
            tmp.append(MealModel(json: record.dictionaryObject!))
        }
        // Display the new loaded page
        self.mealSearch.appendContentsOf(tmp)
        self.tableView.reloadData()
    })
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.mealSearch.count
    }

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
   let cell = tableView.dequeueReusableCellWithIdentifier("SearchCell", forIndexPath: indexPath)
   let cellData = self.mealSearch[indexPath.row]

   cell.textLabel?.text = cellData.food
   cell.detailTextLabel?.text = cellData.drink
 
    if (indexPath.row + 5) >= mealSearch.count {
     self.loadMore()
    }
 
    return cell
 }

Code to delete mealID:

class DeleteController: UIViewController{
var dbRef: FIRDatabaseReference!
var mealIDtoDelete: String?
override func viewDidLoad() {
        super.viewDidLoad()
        self.dbRef = FIRDatabase.database().reference()
    }
override func viewWillAppear() {
        super.viewWillAppear()
        
        let mealRef = self.dbRef.child("meal")
        mealRef.observeSingleEventOfType(.Value, withBlock: {
            (snapshot) in
        
            if let dict = snapshot.value as? [String:AnyObject]{
               let mealID = dict["mealID"] as? String
               self.mealIDtoDelete = mealID!
           }
    }
@IBAction func deleteButton(sender: UIButton) {
        let mealToDelete = self.dbRef.child("meal").child(self.mealIDtoDelete!)
        mealToDelete?.removeValueWithCompletionBlock({
                       (error, ref) in
                if error != nil{
                  print(error?.localizedDescription)
                  return
         }
   }
}
1 Like

Hi @lsamaria, thanks for posting and including lots of detail.

You are right, the Firebase example in our docs needs to be brought up to date, we’ll get someone on that.

I think I see what’s happening. Remove this code from your program:

  **// Import Existing Data**
  rootRef.on('value', initIndex);

  function initIndex(dataSnapshot) {
    // Array of data to index
    var objectsToIndex = [];
    // Get all objects
    var values = dataSnapshot.val();
    // Process each Firebase object
    for (var key in values) {
      if (values.hasOwnProperty(key)) {
        // Get current Firebase object
        var firebaseObject = values[key];
        // Specify Algolia's objectID using the Firebase object key
        firebaseObject.objectID = key;
        // Add object for indexing
        objectsToIndex.push(firebaseObject);
    }
    }
    // Add or update new objects
    index.saveObjects(objectsToIndex, function(err, content) {
      if (err) {
        throw err;
      }
      console.log('Firebase<>Algolia import done');
    });
  }

That shouldn’t run every time you run npm start, but reIndex should. The reIndex will do a full sync of the data to Algolia, which would be necessary if the npm start process wasn’t running for a period of time while there were changes in Firebase. Ideally npm start process is running all the time, but you never know. reIndex is better than initIndex for this case because it not keep deleted records around (initIndex assumes the index was empty, which is not that case).

Your line fb.on('value', reindexIndex); should read rootRef.on('value', reindexIndex);, same for anywhere you use fb as a variable (it doesn’t look like you’ve defined it). You might want to add a try / catch around all the code to log any errors.

Let me know if you can try those things and it helps.

2 Likes

@dzello Hey there, thanks for the help. I’m here let me read what you wrote

I’m going to make the changes and run the code I’ll come right back

@dzello I’m having some Heroku issues right now. I’ll try your advice first thing in the morning and get back to you. THANK YOU VERY MUCH FOR YOUR HELP!!!

2 Likes

@dzello Hey there! I just want to keep you posted that I updated to Swift 3 and the migration caused code problems. As soon as I get everything figured out I will resume the project and get back to you with the results. Thanks again for your help :slight_smile:!

2 Likes

@dzello Hey man, sorry about the longgggg response. I’m a lone developer and I have a really really big app that supports all the iPhones and iPad. There was a ton of bugs and ux/ui problems I had to fix. Anyhow I finally got around to using the answer you posted and it 100% works. I’ve updated to Xcode/Swift 3.2 and it’s all good. Thanks for the help and I apologize for getting back to you so late.

LanceSamaria

1 Like

Hey @LanceSamaria, thanks for posting back and letting me know. Glad you got it working!

1 Like

@dzello hey thanks again for your help. If you get a chance I found new problem and just posted a new question:
Firebase Rules Doesn't Let Algolia Run Node.js Searches Unless Rules Are Set to True.

Can you have someone take a gander at it when they’re free? Thanks!

1 Like

Yes, will have a look, thanks for posting!

Thanks, btw, I’m a solo dev, I’ve been working on this project for 19 months. I have 20,000 lines of code and I’m very very close to launching. I’m going to document everything I’ve encountered from having no programming experience to actually launching an app. I want other people who are solo entrepreneurs to have something to reference if they ever want to take this journey. You’ve been very helpful. I want to include you in my story. I’m going to reference Algolia as my search provider and I want to reference the forums and yourself as the entity and person who helped me with problems. If you’d rather me not reference you I won’t.

4 Likes

Super impressive journey @LanceSamaria! It’s not easy being a solo dev, let alone learning to program, let alone being a solo entrepreneur.

We’re wishing you a lot of success with the launch and are glad to have been part of the journey. Definitely reference me/Algolia in your story, we’ll be happy to help share it!

5 Likes

Thanks for the support! I’ll let you know when the app goes live. The question I just asked and auto layout for the iPhone 8 and X is all I have left. Hopefully I’ll submit to Apple by next week.

1 Like

I actually do have 1 more Algolia question about the 1000 results. It’s not anything technical, I need more of an explanation. I’ll post it tomorrow.

2 Likes

If Node.js code isn’t syncing with the app then it might be an issue as the source code might be wrong.For more visit https://errorcode0x.com/resolve-steam-error-code-80/

@viluwuzan Thanks for the help but I’m confused with what that link has to do with the node.js code not matching up with Firebase and my iOS app? I don’t see the correlation.

Btw the problem was resolved.