DEV Community

Cover image for Retrieve complex data from Firestore using Unity + Firestore SDK for your mobile app (2022)
Tim Sholenbakh
Tim Sholenbakh

Posted on

Retrieve complex data from Firestore using Unity + Firestore SDK for your mobile app (2022)

Hi! In this article I'd like to show you a simple way to get started with retrieving any type of data from Firestore in Unity using the Firestore Package for your mobile app. At the time of writing this article there's no JSON support yet.
The shown method is quite straightforward and does not involve writing our own JSON converters.

⚠️ = Be aware
⏬ = Download

Table of contents:

⚠️ In this tutorial there will be code snippets from my Painting Recognition app. There's no available code repository yet since this app is a schoolproject on which my grades are dependent.


Requirements

In this demo we'll be using:

Preparation

(If you have already done these steps, go to Adding Data or Retrieving Data)

Here's what you need to do to set it all up!

1. Create/Use an Unity project

Tutorial link here.

2. Add Firebase to your Unity project

Tutorial link here.

⚠️ Be sure to enter a Package Name for your app since you'll have to use this later!
Firebase App Management

3. Link Firebase with Unity

Once you've filled in your Package Name you can now do the same in your Unity project:

  • Go to 'File' -> 'Build Settings'
  • Select your Platform
  • Once selected, click on Player Settings
    Player Settings

  • Scroll down until you find Bundle Identifier
    Bundle Identifier

  • Enter the same Package Name you've chosen

  • Next add the Firebase SDK:

    • Extract the .zip SDK
    • Go into the folder dotnet4 since our Unity version is > 2017
    • Go back to Unity and create and open the folder 'Firebase' in 'Assets'
    • Drag and drop the FirebaseFirestore.unitypackage in the 'Firebase' folder (to keep it organised)
  • Finally, download the Project Identifier file from the Firebase Console:

    • Android: ⏬ google-services.json
    • iOS: ⏬ GoogleService-Info.plist google-services file
    • Drop this file into the 'Assets' folder in Unity

Adding Data

Before we can retrieve any data we need to add it first. Be advised to structure your to be retrieved data beforehand. It might save you a lot of time rewriting code!

Firestore is a non-relational (a.k.a. NoSQL) database like MongoDB. There are no columns, no specific order and no foreign keys to create any type of relationship with other data. In Firestore or other NoSQL databases, data is stored in a Collection of Documents.

In this example the data will be structured the following way:
Data Structure

Above is a list of paintings (known as a Collection) wherein each individual painting (known as a Document) resides.

Each document also has its own fields. This is the actual data that belongs to that document.

In this example, the document fields look schematically like this:
Document Scheme

Each painting has a couple of points of interest and each point has multiple descriptions for the different languages that they can be read in.

In Firestore it will look hierarchical:
Hierarchical structure

Now it's up to you! Add data to your Firestore. You can do this manually or through code. If you'd like have more information on structures and how to add data, take a look on the Firestore Documentation: Add Data.

Retrieving Data

Now onto the fun part: <coding> :)! In Unity, add an empty GameObject in the hierarchy and create a new script in Assets > Scripts. Attach this script as a component on the cube and open this script in your favorite editor (I use JetBrains Rider).
It should look like this:
Inspector Add Component

Reference to Firebase

First we need to get a reference to our collection (fill in your collection name):

private Task<QuerySnapshot> _storageSnapshot;
void Awake() => _storageSnapshot = FirebaseFirestore.DefaultInstance.Collection("Paintings").GetSnapshotAsync();
Enter fullscreen mode Exit fullscreen mode

We don't have to worry about opening or closing connections. The code will do its magic by checking our Project Identifier and Package Name.

Coroutines

Next we'll write a coroutine. This method is identified by returning an IEnumerator. Coroutines work like void methods in terms of returning nothing really. To make it work, we add a method as a parameter (also known as a callback) to which we give our result to. In this case it's a list of our retrieved paintings.

A coroutine is Unity's way of handling asynchronous operations and multithreading. We can start iterating on the retrieved collection by creating an anonymous method to continue the Task:

public IEnumerator GetPaintingsAndPois(Action<IList<Painting>> callback)
{
     IList<Painting> paintingList = new List<Painting>();
     int paintingCount = 0;
     _storageSnapshot.ContinueWithOnMainThread(task =>
     {
          var collection = task.Result;
          if (collection.Count == 0)
              Debug.LogError("There are no paintings available in the collection 'Paintings'.");

          paintingCount = collection.Count;
          Debug.Log("Found " + paintingCount + " paintings!");
     }
     yield return new WaitUntil(() => paintingList.Count == 
     paintingCount && paintingList.Count != 0);
     Debug.Log("Done getting " + paintingCount + " paintings!");
     callback(paintingList);
}
Enter fullscreen mode Exit fullscreen mode

With coroutines you can stop execution until a certain predicate is met. In this example I wait until it has retrieved all the paintings. Due to the synchronous way of how the paintings are retrieved (no async/await keywords), the WaitUntil() is very important. I have tried using async/await but I ran into order of execution and threading issues. As said previously, coroutines handle this for you.

At this stage, we've gotten all our paintings but did nothing with them. Let's take a look on how to iterate over them.

(If you'd like to see the boilerplate code for this, you can visit the Firestore Documentation: Get Data)

Dictionaries

Since there's no JSON support, we have to make it work the cumbersome way with dictionaries. Dictionaries in particular are very useful for combining data into Key-Value Pairs. In our case it's going to be matching a string (name of the key) to an object (fields from a Document). Value is an object so we can cast it to any type we need.

Let's start with unstructured, easy to access data like the painting's title. For this we need to loop over each Document in our retrieved collection:

//...
//paintingCount = collection.Count;
//Debug.Log("Found " + paintingCount + " paintings!");

foreach (var paintingData in collection.Documents)
{
      string titlePainting = ???
}
Enter fullscreen mode Exit fullscreen mode

To retrieve a Value from a dictionary, you can use the [] operator. In code it will look like this:

paintingData.ToDictionary()["title"]
Enter fullscreen mode Exit fullscreen mode

This will give you the Value for the key "title". Using this method we can now loop over each painting/document and get our field by safe casting it (using 'as') to the right type at the end:

foreach (var paintingData in collection.Documents)
{
      string titlePainting = paintingData.ToDictionary()["title"] as string
      //string painter = ...
}
Enter fullscreen mode Exit fullscreen mode

Retrieving an array

Now we can apply this method to every other field except to an array. Let's say we want to retrieve the PointsOfInterest[] array from our painting.
POI Structure

You might think we can apply the same technique using [] on the dictionary and then safe cast it to object[] but it will result in a NullPointerException. It goes against intuition since it is an array in Firestore.
Array Firestore

What does work however is safe casting it to List<object>:

List<object> pointsOfInterest = painting.ToDictionary()["pois"] as List<object>;
Enter fullscreen mode Exit fullscreen mode

Retrieving nested arrays

Now we've covered how to get an array out of a Document but... how do we get an array out of an array you might ask. Luckily the answer is somewhat the same.

Since we have a List<PointsOfInterest> we can iterate it and safe cast our object to a Dictionary<string, object> to be able to retrieve data from it:

foreach (object poi in pointsOfInterest)
{
      Dictionary<string, object> poiDictionary = poi as Dictionary<string, object>;
}
Enter fullscreen mode Exit fullscreen mode

And now you can simply use [] to get the descriptions and safe cast them to a List<object>:
Descriptions

foreach (object poi in pointsOfInterest)
{
      Dictionary<string, object> poiDictionary = poi as Dictionary<string, object>;

      //Another array...
      List<Description> = poiDictionary["descriptions"] as List<object>;
      //string language ...
}
Enter fullscreen mode Exit fullscreen mode

Result

Everything combined, the coroutine's structure should look similar to this:

public IEnumerator GetPaintingsAndPois(Action<IList<Painting>> callback)
{
  //...
  _storageSnapshot.ContinueWithOnMainThread(task =>
  {
    var collection = task.Result;
    foreach (var paintingData in collection.Documents)
    {
      string titlePainting = paintingData.ToDictionary()["title"] as string
      //string painter = ...

      List<object> pointsOfInterest = painting.ToDictionary()["pois"] as List<object>;
      foreach (object poi in pointsOfInterest)
      {
        Dictionary<string, object> poiDictionary = poi as Dictionary<string, object>;
        List<Description> = poiDictionary["descriptions"] as List<object>;
        //string language...
      }
    }
  }
  //...
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

And that's it! You've successfully gotten all the fields you needed. Now you can work on the more fun things in your app.

If you have any issues, feel free to let me know down below in the comments :)!

Discussion (2)

Collapse
bharath09vadde profile image
BHARATH CHANDRA REDDY VADDE

Hey I have been trying to get the nested data in to Unity from Firestore database. Can you please help me with this?

https://www.reddit.com/user/bharath09vadde/comments/u2pc59/how_to_get_nested_data_from_the_firestore/?utm_source=share&utm_medium=web2x&context=3

Collapse
timsholenbakh profile image
Tim Sholenbakh Author • Edited on

Hi!

I have never used the Result.ConvertTo method and I'm not sure the automatic JSON convertion works for nested data as you've shown on reddit but you can try to get the languages using the method described above and fill them in manually. The code might not be copy paste ready:

1. Iterate over your Documents:
foreach document in task.Result.Documents (inside the ContinueWithOnMainThread)
2. Convert each Document to a dictionary and get the needed value/object using the '[]' operator:
in the foreach: List<object> languages = document.ToDictionary()["languages"] as List<object>;
3. If you want to get the links from the english (en) language you need to iterate over the List<object> languages:
foreach language in languages
4. Inside this iteration, convert the language to a dictionary and get the value (the array 'links') using '[]':
List<object> links = language.ToDictionary()["links"] as List<object>;
5. Now you can iterate to get further fields :)