How to manage your native iOS and Android dependencies in Unity like Firebase
Common issues and their root
There are a few reasons why you might have found this article. One is that you integrated the Firebase SDK and suddenly you started getting errors about duplicate symbols on iOS or duplicate classes on Android, or perhaps you integrated another library into an existing Firebase game and received these errors. You may also be someone maintaining a native plugin or planning on maintaining one, and you’re trying to figure out how to avoid asking developers to customize platform specific build files. Perhaps you just found a bug that was fixed when someone told you to run “Force Resolve” in the Play Services Resolver menu and you want to understand this magical incantation.
One of the reasons why I love using Unity is that I really feel like I can write a game once and deploy it across a wide range of platforms. Every once in a while I will run into a use case where Unity just hasn’t abstracted some piece of underlying operating system logic that I need. When this occurs, C# has a really powerful foreign function interface and awesome convenience layers that help me not only call into C libraries but to whatever other native layer many platforms use.
Firebase found itself in this exact situation. It uses a lot of iOS and Android specific logic on each platform to take advantage of platform specific optimizations and better integrate with native SDKs where possible. Since game developers are so familiar with C++, Firebase wrapped everything up in a nice C++ layer. The Unity SDK is built upon this abstraction to benefit both from the games-focused design of the C++ SDK and the platform specific optimizations of the individual native SDKs. This means that when you unwind this stack, you still find frameworks for iOS and AAR libraries for Android that you have to keep up to date and in sync with your Unity plugin.
Unity does automatically find and include native libraries when you drop them in the Assets directory. This would work great if Firebase were entirely self contained, but plugins like Firebase rely on a lot of transitive dependencies that it may share in common with other native libraries. Developers often find themselves with a complicated graph of dependent libraries that they have to manage by hand, colloquially known as “Dependency Hell.”
Just finding and resolving the dependencies is one part of the issue. Sometimes platform specific build rules need to be configured as well, or you need to reference libraries built into the system that you can’t or shouldn’t duplicate in your Unity project. When this occurs, you may end up with a complex network of conflicting postprocessing scripts from your various native plugins that you have to hand prune or extend to get your game into a shippable state.
How does the Firebase plugin fix this?
Like the Firebase SDK itself, Google opts to use dependency managers optimized for each target platform rather than creating new tools from scratch. This means that iOS native plugins will use CocoaPods and Android ones will use Maven (the system Gradle uses behind the scenes to find external dependencies). The Unity plugin then uses a custom tool called the Play Services Resolver to unify both of these dependency management systems under one interface. This means that any Unity plugin that relies on native code can use this system to safely handle native dependencies on iOS and Android build targets!
Let’s dig into the resolver:
You need two parts to get the resolver to function. First, you need a file named
*Dependencies.xml. This file should be placed in an Editor directory for it to function, which avoids Unity accidentally pulling it into your shipping product.
Then you need the Play Services Resolver itself, which will periodically look for these files and seek out their documented dependencies. When this occurs depends on how you’ve configured the resolver, but the end result will be the dependencies expressed in a form that each platform’s native build system can process for you.
If you only care about a specific platform, feel free to jump this to either the iOS or Android section. But before you go, make sure that you have the Play Services Resolver installed. If you’re already using the Firebase Unity Plugin, this has already happened.
Often times, you may be relying on native plugins in Unity that either replicate some transitive dependency (such as the Android Jetpack libraries) or ask that you make changes to the
mainTemplate.gradle file in Android. If you’re a plugin developer for an Android specific Unity plugin, you can use the steps outlined below to automate this for your users. Similarly if you’re a developer, I’d like to help you start to manage these dependencies so you spend more time making a game and less time playing hide and seek with jar files.
For demo purposes, I’ve created a simple class called
NotificationHelper that will facilitate showing notifications on Android:
With recent versions of Unity using the gradle build system, you can actually just drop a
.kt file into
Assets/Plugins/Android to get Unity to add it to the build (some restrictions apply, see site for details). Unfortunately, notifications require that I include a drawable resource, and for that reason I build this into an android aar library. I won’t cover how to turn this into an aar repository, it shouldn’t be that hard if you’re an existing Android developer. What you need to pay attention to is the
dependencies section of your module level
I’ll want to create a plugin called
NotificationHelper that exposes this to other developers. So first I create a folder under Assets called
NotificationHelper, then I create a folder inside that called Editor. Finally, I create an xml file called
NotificationHelperDependencies.xml with the following contents:
to match my
I’ll do a trial resolve to make sure it all works:
If resolution succeeds, you should now see
Assets/Plugins/Android fully populated with all of your necessary Android dependencies:
I love this for multiple reasons. First, that is a whole lot of work I just saved chasing down various libraries. Second, I love seeing exactly what I’m pulling into my project. This isn’t the only way this could have been resolved though. If I used an older version of Unity, the Play Services Resolver would then process these aar files to pull out jar files to include. This means that if you’re careful, the Play Services Resolver can help you backport your library all the way to Unity 4.7.
If I let the Play Services Resolver patch my
And configure a gradle file in my project settings:
You’ll see that your
mainTemplate.gradle now has all of your dependencies included:
This is really cool because if you have to patch the
mainTemplate.gradle for any other reason, perhaps you’re applying a gradle plugin, you can see everything in one place. It’s also super familiar for developers used to Android development. If you have another plugin that wants you to add a library dependency to the
mainTemplate.gradle file, it is still probably best to adapt it to use the Play Services Resolver as it will remove redundant
implementation lines and make it easier to clean up if you swap out a plugin.
However I resolve my dependencies, I can now easily expose my Java class to my C# logic:
Hopefully, at this point, Firebase’s dependency management solution is a little more clear. Rather than trying to retroactively go through the Android Firebase quickstart to pull all your library dependencies into
mainTemplate.grade or maintain all your transitive dependencies by hand, consider building upon all the work that’s gone into automating this work for you in the Play Services Resolver.
It isn’t as common for iOS developers to use dependency management. In fact, I know that there is some trepidation around the very topic (in the past, I was one of those developers). To see why this is such an important tool in Unity, let’s first jump over to the iOS Firebase page outlining the steps needed to integrate the Firebase SDK without CocoaPods. First I need to download an extra zip file, which includes a
README.md file that outlines the dependencies of each library. Analytics is common to most of them, so I can read that and see that I need:
Now, for demo purposes, I’ll pull in the Firebase Analytics SDK without the Play Services Resolver:
And drop in the required frameworks manually:
Right now the build succeeds, but there’s an interesting thing with this list of dependencies. NanoPB is a popular 3rd party framework for dealing with protocol buffers in resource constrained environments. It’s not unreasonable to expect another plugin to start using them, especially if that plugin does something with gRPC.
For instance, maybe I have an awesome character customization solution that stores all its data in protocol buffers:
When I add this second plugin and hit build, I get a build failure about duplicate libraries:
This this moderately contrived example, it would be easy to resolve this by just disabling one NanoPB framework and leaving the other. I would then need to do this every time I update one of the plugins, making sure the binary versions are kept in sync, and making sure I prune any related compiler flags. This becomes quite the maintenance headache, especially if I ever want to hand the reins of this game off to anyone else.
How does the Play Services Resolver fix this issue?
If I instead import Firebase Analytics with the Play Services Resolver and inspect the
AnalyticsDependencies.xml file, I see that there are no explicit dependencies on NanoPB:
But if I build for device, I can see it appear in my XCode project all thanks to CocoaPods!
If you receive my AvatarCustomizer plugin, how can you make it aware of the Play Services Resolver as well (or, if you’re developing a plugin, how do you do this from the get-go)? First you should go into
AvatarCustomizer and make an
Editor folder. Then add a file named
AvatarCustomzierDependencies.xml into which you’ll copy the sample Dependencies.xml file from the Play Services Resolver git repository. Right now it looks like this:
The framework you want to move into the resolver is NanoPB, so you should find it on CocoaPods. When you click on “Installation Guide” it gives you the following sample:
So go back to your
AvatarCustomzierDependencies.xml file and change it to point to that dependency:
When you build, everything just works. To even see that anything happened you would have to open up the generated
Podfile (at the root of your output directory) to see
nanopb added as a dependency:
Podfile is effectively the output of the Play Services Resolver on iOS. The resolver does a few more fancy things to make sure your output project is configured properly, but most of the work is properly generating this output file. If you’re a seasoned iOS developer, this is probably more reassuring than if the resolver performed a ton of custom magic under the hood. If not, you can still rest easy in knowing that this relies on a large community driven open source dependency manager rather than an opaque Google specific tool.
To Firebase, and Beyond!
Once you start digging into the Firebase plugins, you will see that each asset package includes the Play Services Resolver and a
Dependencies.xml file to help manage native requirements. You should now be moderately comfortable reading these configuration files and understanding what and how makes it into your final game.
As game developers, I know that you want to focus on building your game rather than micro managing the scaffolding it’s built upon. The whole goal of Firebase and libraries like it is to get the solved problems out of the way so you can focus on new and interesting challenges. Likewise my goal here is to highlight an otherwise little known corner of the SDK to help you along with that process!