Friday, March 10, 2017

Parsing Mad XML To Snag Some Sweet, Sweet Board Game Data Using BGG's API

Check it.

API: Application Programming Interface
XML: Extensible Markup Language
BGG: Board Game Geek

Now that you're caught up on all of the initialisms in the title of this article, let's write a simple app that's lets us use some of that data.  But what should it do?  Well, I don't know about you doofuses, but I spend almost as much time trying to figure out which game in my collection I'd like to play next as I do playing them.  So here's what we're going to do: make our phones decide for us.  Let's do it.

Let's start with the User Interface.

Game Grabber?  Eh, that's cool.  I can live with that. That text field there is where we can plug in any BGG username to tell our app whose collection to choose the game from. The Game label toward the bottom is going to be what we use to display the name of the game that our phone decides we should play.  Don't worry, its going to stay hidden until the game is actually chosen.

Aight, lets jump into our View Controller and get coding.

Okay, we know we have to make our View Controller a delegate for our text field, but what in the heck is an XMLParserDelegate?  It's a Protocol we're adopting to become a delegate for an XML Parser, you dingus!  All will make sense soon.  Let's move on.

Okay, what do we have we here.  Outlets for our text field and label, an array of strings for the game titles we're going to snag from BGG, a blank string called currentTitle, and a bool keeping track of whether or not our XML document is currently being parsed.  Not bad, not bad.  Let's set up our viewDidLoad next.

Coolio.  Here we set our usernameField delegate to the View Controller, and set up a gesture recognizer for out view and give it an action called dismissKeyboard.  Still no sign of that XMLParserDelegate, though.  Hmmm, I'm sure it will come into play later.  For now let's take a look at dismissKeyboard in more detail.

Well, I don't know what I expected... it dismisses the on-screen keyboard.  Got it.  I wonder what's going on with that text field anyways?

Very nifty.  First thing we do when the user presses the return button and starts their search is check if the gameTitles array is empty.  This is to make sure that if we've already got some game data from a previous API request we don't have to make another one.  We can just take a game out of the titles we already nabbed.

After that is where we are ensuring that randomGameLabel is staying hidden until we put a game title in it.  We also set documentBeingParsed to true because we are about to start digging into some XML!  There are a few checks to make sure of first though: first that the usernameField actually has some text in it, and secondly that there are no spaces in it.  You cannot have spaces in your BGG username!  Unless I missed a memo or something, I probably did.

Whatever, lets get on the net.

There you go, hitting that API like a sledgehammer.  We're snagging the username from our text field and passing that in as part of the URL, but what's that little ?own=1 doohickey?  That's a parameter we're passing in called own with a value of 1.  What that does is ensure that the game our phone spits back out to us is a game we actually own.  The whole point here is to be able to actually play the game that shows up on our screen, remember?  I supposed you could pass in a value of 0 if you wanted your phone to tell you what game you should bur next or something, but I digress.

Pretty standard session and dataTask stuff.  There are two more guards in there making sure we're actually getting a response and usable data back.  The only real standout is that sick switch statement right in the middle of the task.  Finally, our XMLParserDelegate is coming into play.  Notice, however, that this is only the case if the statusCode we get back from BGG is exactly 200, which indicates the data is ready for our perusal.  Otherwise, we break out of the closure.

The funny thing about the BGG API is that it always fails the first time you make a new request.  This is because their servers work on a queuing system.  Think of your first request like knocking on their door, and it isn't until you make another, slightly more polite request, that you're actually let inside.  We'll handle this in a little more detail later, but for now let's look more deeply into our XML Parser.

First things first, we create an XMLParser called xmlParser and we tell it that it's going to be chowing down on the response data we wrestled out of the API.  Then we assign the View Controller as its delegate, and lastly we tell it to GO.  TO.  TOWN.  Let's look into more detail at what our parser is actually doing.

Our first parser delegate function is didStartElement.  This bad simply tells the currentTitle string we declared way up above to empty out its contents every single time it hits a new Element in our XML file.  An element in XML looks like this:


So this function fires off every time it hits that first set of brackets.  The reason we want this is to happen is to make sure we can snag every element's String individually and keep our data nice and tidy.

The foundCharacters function continually fires off as it makes its way down the XML file.  Get it now?  That's why we have to make sure at the start of each new element we're clearing our currentTitle string.  Otherwise this function will just keep adding more and more characters to the same darn string!

At the END of each element we check to see if it is a "name" element, if it is we set the currentTitle string equal to a gameTitle variable and append it to our array of gameTitles.  Now you should understand the true nature of XML Parsing!

Lastly, we have this parserDidEndDocument function that switches the documentBeingParsed bool back to false.  This has adds no functionality whatsoever to our app, but I used it for debugging and forgot to take it out.  Sue me!

Okay, we are almost done.  Our dataTask is finished, now we just have to do something with all that data we just got.

If at this point our gameTitles array is still zero, we throw a message up onto the screen to let the user know to try again in a couple of seconds (a bad search will have been caught by an earlier guard statement, so this is in case BGG's API stubbornly decides not to give us any data like it's been known to do).  Otherwise, we got some game titles now to play with!  Heck yes!  Let's randomly select one and spit it up to the screen.

There is one more thing we have to watch out for though.  What if you want to switch between multiple BGG collections by searching different users back to back?  We need an elegant way to flush out the gameTitles array and get it prepared for some new ones.  We didn't include that in the actual search function because we wanted to avoid calling the API over and over again for the same user's collection.  How about this:

There we go.  If the user decides to change the text in the text field, we'll flush the array out then.

That's it, buddy!  Let's see this bad boy in action.  Bear in mind, in the following gif I'm doing searches I've already done several times before which is why I'm not getting hit with the message saying to try again.  Typically, the first request will tell you to cool off and wait for a bit, but that's ok I still love you BGG :)

I love it.  I love everything.  I love board games.  I love Swift.

Fare thee well, bozos.

No comments:

Post a Comment