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:
-
⏬ Unity Hub (Personal License) -> Unity 2020.3.26f1
- Modules (2): Android & iOS Build Support
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!
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
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)
- Extract the
-
Finally, download the
Project Identifier
file from the Firebase Console:- Android: ⏬
google-services.json
- iOS: ⏬
GoogleService-Info.plist
- Drop this file into the 'Assets' folder in Unity
- Android: ⏬
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:
Above is a list of paintings (known as a
Collection
) wherein each individual painting (known as aDocument
) 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:
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:
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:
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();
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);
}
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 = ???
}
To retrieve a Value
from a dictionary, you can use the []
operator. In code it will look like this:
paintingData.ToDictionary()["title"]
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 = ...
}
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.
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.
What does work however is safe casting it to List<object>
:
List<object> pointsOfInterest = painting.ToDictionary()["pois"] as List<object>;
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>;
}
And now you can simply use []
to get the descriptions and safe cast them to a List<object>
:
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 ...
}
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...
}
}
}
//...
}
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 :)!
Top comments (2)
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
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 :)