mirror of
https://github.com/LukeHagar/unicorn-utterances.git
synced 2025-12-06 12:57:44 +00:00
Finish initial draft of article
This commit is contained in:
@@ -32,6 +32,12 @@ Unforunately, I've had difficulties getting the same Android Studio development
|
||||
|
||||

|
||||
|
||||
Once copying the files from the Android Studio environment to `Assets` is finished, you'll need to mark it as being included in the Android build within Unity's inspector window that comes up when you highlight the source file
|
||||
|
||||

|
||||
|
||||
> If you forget to do this, your class or file may not be found. This is an important step to keep in mind during debugging
|
||||
|
||||
This will naturally incur a question for developers who have tried to maintain a system of duplication of any size:
|
||||
**How do you manage dependencies between these two folders?**
|
||||
|
||||
@@ -40,7 +46,7 @@ This will naturally incur a question for developers who have tried to maintain a
|
||||
|
||||
Luckily for us, managing Android code dependencies in Unity has a thought-out solution from a large company: Google. [Because Google writes a Firebase SDK for Unity](https://firebase.google.com/docs/unity/setup), they needed a solid way to manage native dependencies within Unity.
|
||||
|
||||
### Installing the Unity Jar Resolver
|
||||
### Installing the Unity Jar Resolver {#installing-jar-resolver}
|
||||
|
||||
> ℹ️ If you've installed the Unity Firebase SDK already, you may skip the step of installing
|
||||
|
||||
@@ -58,17 +64,17 @@ Then, you'll see a dialog screen that'll ask what files you want to import with
|
||||
|
||||
> Your screen may look slightly different from the one above. That's okay - so long as all of the files are selected, pressing "Import" is perfectly fine
|
||||
|
||||
### Using the Jar Resolver
|
||||
### Using the Jar Resolver {#using-jar-resolver}
|
||||
|
||||
Using the Jar resolver is fairly straightforward. Whenever you want to use a depenedency in your Android code, you can add them to a file within [the `Assets/AndroidCode` folder](#setup-a-development-environment) that adds dependencies with the same keys as you'd typically find in a `build.gradle` file for dependencies
|
||||
|
||||
```xml
|
||||
<!-- DeviceNameDependencies.xml -->
|
||||
<dependencies>
|
||||
<androidPackages>
|
||||
<androidPackage spec="com.jaredrummler:android-device-names:1.1.8">
|
||||
</androidPackage>
|
||||
</androidPackages>
|
||||
<androidPackages>
|
||||
<androidPackage spec="com.jaredrummler:android-device-names:1.1.8">
|
||||
</androidPackage>
|
||||
</androidPackages>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
@@ -78,20 +84,20 @@ The only rule with this file structure is that your file must end with `Dependen
|
||||
<!-- LocationCodeDependencies.xml -->
|
||||
<!-- Alongside the other file -->
|
||||
<dependencies>
|
||||
<androidPackages>
|
||||
<androidPackage spec="com.google.android.gms:play-services-location:16.0.0">
|
||||
</androidPackage>
|
||||
</androidPackages>
|
||||
<androidPackages>
|
||||
<androidPackage spec="com.google.android.gms:play-services-location:16.0.0">
|
||||
</androidPackage>
|
||||
</androidPackages>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
Then, after creating the files, in the menubar, go to `Assets > Play Services Resolver > Android Resolver > Resolve` and it should go fetch the AAR files related to those specific libraries and download them.
|
||||
|
||||

|
||||

|
||||
|
||||
So long as your file ends with `Dependencies.xml`, it should be picked up by the plugin to resolve the AAR files.
|
||||
|
||||
#### Adding Support Into Android Studio Environment
|
||||
#### Adding Support Into Android Studio Environment {#add-android-studio-support}
|
||||
|
||||
But that's only half of the equation. When editing code in Android Studio, you won't be able to use the libraries you've downloaded in Unity. This means that you're stuck manually editing both of the locations for dependencies. This is where a simple trick with build files comes into play.
|
||||
|
||||
@@ -99,7 +105,7 @@ Assuming, like me, you used the built-in "Create Project" method of starting a c
|
||||
|
||||
```groovy
|
||||
dependencies {
|
||||
implementation fileTree(dir: '../../Assets/Plugins/Android', include: ['*.jar', '*.aar'])
|
||||
implementation fileTree(dir: '../../Assets/Plugins/Android', include: ['*.jar', '*.aar'])
|
||||
}
|
||||
```
|
||||
|
||||
@@ -125,14 +131,13 @@ You must make your callback extend the type of callback that is used in the libr
|
||||
|
||||
```java
|
||||
DeviceName.with(context).request(new DeviceName.Callback() {
|
||||
@Override public void onFinished(DeviceName.DeviceInfo info, Exception error) {
|
||||
String manufacturer = info.manufacturer; // "Samsung"
|
||||
String name = info.marketName; // "Galaxy S8+"
|
||||
String model = info.model; // "SM-G955W"
|
||||
String codename = info.codename; // "dream2qltecan"
|
||||
String deviceName = info.getName(); // "Galaxy S8+"
|
||||
// FYI: We are on the UI thread.
|
||||
}
|
||||
@Override public void onFinished(DeviceName.DeviceInfo info, Exception error) {
|
||||
String manufacturer = info.manufacturer; // "Samsung"
|
||||
String name = info.marketName; // "Galaxy S8+"
|
||||
String model = info.model; // "SM-G955W"
|
||||
String codename = info.codename; // "dream2qltecan"
|
||||
String deviceName = info.getName(); // "Galaxy S8+"
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
@@ -141,11 +146,11 @@ While this example may seem straightforward, let's disect what we're doing step-
|
||||
```java
|
||||
// Create a new "DeviceName.Callback" instance
|
||||
DeviceName.Callback handleOnFinished = new DeviceName.Callback() {
|
||||
// Provide an implementation of the `onFinished` function in the `Callback` class
|
||||
// Notice that there are two parameters for this method: one for info, the other for errors
|
||||
@Override public void onFinished(DeviceName.DeviceInfo info, Exception error) {
|
||||
// ... Assignment logic here
|
||||
}
|
||||
// Provide an implementation of the `onFinished` function in the `Callback` class
|
||||
// Notice that there are two parameters for this method: one for info, the other for errors
|
||||
@Override public void onFinished(DeviceName.DeviceInfo info, Exception error) {
|
||||
// ... Assignment logic here
|
||||
}
|
||||
};
|
||||
|
||||
// Create a `DeviceName.Request` by passing the current context into the `DeviceName.with` method
|
||||
@@ -172,9 +177,9 @@ In order to create an instance of a `Callback` in C# code, we first need a C# cl
|
||||
```c#
|
||||
private class DeviceCallback : AndroidJavaProxy
|
||||
{
|
||||
// `base` calls the constructor on `AndroidJava` to pass the path of the interface
|
||||
// `$` refers to interface name
|
||||
public DeviceCallback() : base("com.jaredrummler.android.device.DeviceName$Callback") {}
|
||||
// `base` calls the constructor on `AndroidJava` to pass the path of the interface
|
||||
// `$` refers to interface name
|
||||
public DeviceCallback() : base("com.jaredrummler.android.device.DeviceName$Callback") {}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -189,17 +194,17 @@ This [`AndroidJavaObject` type has a myriad of methods that can be called to ass
|
||||
```c#
|
||||
private class DeviceCallback : AndroidJavaProxy
|
||||
{
|
||||
public DeviceCallback() : base("com.jaredrummler.android.device.DeviceName$Callback") {}
|
||||
// These both MUST be `AndroidJavaObject`s. If not, it won't match the Java method type and therefore won't be called
|
||||
void onFinished(AndroidJavaObject info, AndroidJavaObject err)
|
||||
{
|
||||
// When running `AndroidJavaObject` methods, you need to provide a type for the value to be assigned to
|
||||
var manufacturer = info.Get<string>("manufacturer"); // "Samsung"
|
||||
var readableName = info.Get<string>("marketName"); // "Galaxy S8+"
|
||||
var model = info.Get<string>("model"); // "SM-G955W"
|
||||
var codename = info.Get<string>("codename"); // "dream2qltecan"
|
||||
var deviceName = info.Call<string>("getName"); // "Galaxy S8+"
|
||||
}
|
||||
public DeviceCallback() : base("com.jaredrummler.android.device.DeviceName$Callback") {}
|
||||
// These both MUST be `AndroidJavaObject`s. If not, it won't match the Java method type and therefore won't be called
|
||||
void onFinished(AndroidJavaObject info, AndroidJavaObject err)
|
||||
{
|
||||
// When running `AndroidJavaObject` methods, you need to provide a type for the value to be assigned to
|
||||
var manufacturer = info.Get<string>("manufacturer"); // "Samsung"
|
||||
var readableName = info.Get<string>("marketName"); // "Galaxy S8+"
|
||||
var model = info.Get<string>("model"); // "SM-G955W"
|
||||
var codename = info.Get<string>("codename"); // "dream2qltecan"
|
||||
var deviceName = info.Call<string>("getName"); // "Galaxy S8+"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -240,36 +245,82 @@ Line-by-line explainations are great, but often miss the wholistic image of what
|
||||
|
||||
```c#
|
||||
public class DeviceInfo {
|
||||
public string manufacturer; // "Samsung"
|
||||
public string readableName; // "Galaxy S8+"
|
||||
public string model; // "SM-G955W"
|
||||
public string codename; // "dream2qltecan"
|
||||
public string deviceName; // "Galaxy S8+"
|
||||
public string manufacturer; // "Samsung"
|
||||
public string readableName; // "Galaxy S8+"
|
||||
public string model; // "SM-G955W"
|
||||
public string codename; // "dream2qltecan"
|
||||
public string deviceName; // "Galaxy S8+"
|
||||
}
|
||||
|
||||
class DeviceName : MonoBehaviour {
|
||||
private class DeviceCallback : AndroidJavaProxy {
|
||||
// Add in a field for us to gain access to the device info after the callback has ran
|
||||
public DeviceInfo deviceInfo;
|
||||
public DeviceCallback() : base("com.jaredrummler.android.device.DeviceName$Callback") {}
|
||||
void onFinished(AndroidJavaObject info, AndroidJavaObject err) {
|
||||
deviceInfo.manufacturer = info.Get<string>("manufacturer");
|
||||
deviceInfo.readableName = info.Get<string>("marketName");
|
||||
deviceInfo.model = info.Get<string>("model");
|
||||
deviceInfo.codename = info.Get<string>("codename");
|
||||
deviceInfo.deviceName = info.Call<string>("getName");
|
||||
}
|
||||
}
|
||||
private class DeviceCallback : AndroidJavaProxy {
|
||||
// Add in a field for us to gain access to the device info after the callback has ran
|
||||
public DeviceInfo deviceInfo;
|
||||
public DeviceCallback() : base("com.jaredrummler.android.device.DeviceName$Callback") {}
|
||||
void onFinished(AndroidJavaObject info, AndroidJavaObject err) {
|
||||
deviceInfo.manufacturer = info.Get<string>("manufacturer");
|
||||
deviceInfo.readableName = info.Get<string>("marketName");
|
||||
deviceInfo.model = info.Get<string>("model");
|
||||
deviceInfo.codename = info.Get<string>("codename");
|
||||
deviceInfo.deviceName = info.Call<string>("getName");
|
||||
}
|
||||
}
|
||||
|
||||
private void Start() {
|
||||
var player = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
|
||||
var activity = player.GetStatic<AndroidJavaObject>("currentActivity");
|
||||
var jc = new AndroidJavaClass("com.jaredrummler.android.device.DeviceName");
|
||||
var withCallback = jc.CallStatic<AndroidJavaObject>("with", activity);
|
||||
var deviceCallback = new DeviceCallback();
|
||||
withCallback.Call("request", deviceCallback);
|
||||
Debug.Log(deviceCallback.deviceInfo.deviceName);
|
||||
}
|
||||
private void Start() {
|
||||
var player = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
|
||||
var activity = player.GetStatic<AndroidJavaObject>("currentActivity");
|
||||
var jc = new AndroidJavaClass("com.jaredrummler.android.device.DeviceName");
|
||||
var withCallback = jc.CallStatic<AndroidJavaObject>("with", activity);
|
||||
var deviceCallback = new DeviceCallback();
|
||||
withCallback.Call("request", deviceCallback);
|
||||
Debug.Log(deviceCallback.deviceInfo.deviceName);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Calling Source Code from Unity {#call-source-from-unity}
|
||||
|
||||
Calling native Android code can be cool, but what if you have existing Android code you want to call from Unity? Well, that's supported as well. Let's take the following Kotlin file:
|
||||
|
||||
```kotlin
|
||||
// Test.kt
|
||||
package com.company.example
|
||||
|
||||
import android.app.Activity
|
||||
import android.util.Log
|
||||
|
||||
class Test() {
|
||||
fun runDebugLog() {
|
||||
Log.i("com.company.example", "Removing location updates")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Assuming you [copied it over to the `Assets/AndroidCode` folder and marked it to be included in the Android build](#setup-a-development-environment), you should be able to use the `package` name and the name of the class in order to run the related code.
|
||||
|
||||
```c#
|
||||
var testAndroidObj = new AndroidJavaObject("com.company.example.Test");
|
||||
testAndroidObj.Call('runDebugLog')
|
||||
```
|
||||
|
||||
# AndroidManifest.XML Overwriting {#manifest-file}
|
||||
|
||||
Many Android app developers know how important it can be to have the ability to customize their manifest file. By doing so, you're able to assign various metadata to your application that you otherwise would be unable to. Luckily for us, Unity provides the ability to overwrite the default XML file.
|
||||
|
||||
By placing a file under `Assets\Plugins\Android\AndroidManifest.xml`, you're able to add new values, change old ones, and much more.
|
||||
|
||||
If you want to find what the default manifest file looks like, you'll want to look for the following file: `<UnityInstallationDirecory>\Editor\Data\PlaybackEngines\AndroidPlayer\Apk\AndroidManifest.xml`. This will provide you a good baseline to copy into your project to then extend upon. The reason I suggest starting with the default XML is that Unity requires it's own set of permissions and such. After that, however, you're able to take the manifest and customize it to your heart's content.
|
||||
|
||||
> It's worth mentioning that if you use Firebase Unity SDK and wish to provide your own manifest file, you'll need to [customize the default manifest file to support Firebase opperations](https://firebase.google.com/docs/cloud-messaging/unity/client#configuring_an_android_entry_point_activity)
|
||||
|
||||
# Firebase Support {#firebase}
|
||||
|
||||
Let's say you're one of the users who utilizes the Firebase SDK for Unity. What happens if you want to send data from Android native code or even use background notification listeners in your mobile app?
|
||||
|
||||
You're in luck! Thanks to the Unity Firebase plugin using native code in the background, you're able to shared your configuration of Firebase between your native and Unity code. So long as you've [configured Firebase for Unity properly](https://firebase.google.com/docs/cloud-messaging/unity/client#add-config-file) and [added the config change to Android Studio](#add-android-studio-support) you should be able to simply call Firebase code from within your source files and have the project configs carry over. This means that you don't have to go through the tedium of setting up and syncronizing the Unity and Android config files to setup Firebase - simply call Firebase code from your source files and you should be good-to-go! No dependency fiddling required!
|
||||
|
||||
# Conclusion {#conclusion}
|
||||
|
||||
I hope this article has been helpful to anyone hoping to use Android code in their Unity mobile game, I know how frustrating it can be sometimes to get multiple moving parts to mesh together to work. Rest assured, once it does it's a satisfying result knowing that you're utilizing the tools that Unity and the Firebase team have so graciously provided to game developers.
|
||||
|
||||
If there are any questions or comments, please leave them down below. Thanks for reading!
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
Reference in New Issue
Block a user