Unity Android plugin tutorial (3/3) Class architecture

Creating Unity Android plugin alongside with other native (iOS) plugin counterparts will end up in a single Unity C# class where plugin can be used, can be hooked up to the rest of the application. So beside some implementation details on Fragment based Unity Android plugin scenario, this article is mainly focus on the Unity part.

To create a multi-platform native Unity plugin, like exposing native alert views pictured herein, need a common / platform-independent abstraction of the desired features.

To create a multi-platform native Unity plugin, like exposing native Alert views pictured herein, need a common / platform-independent abstraction of the desired features.

Preliminary tutorials

To see the backgrounds of the Fragment based plugin, please read Unity Android plugin tutorial 1. Fundamentals. The project setup that the Unity Android plugin uses throughout this article is based entirely on the structure that is pictured in Unity Android plugin tutorial 2. Project setup.

While the article may seem a bit dense at the first sight, the codes will be short and simple I promise.

TL;DR

While I think it is better to understand the background of a particular design, the entire Unity project source of eppz! Alert is available at GitHub for people who only read code, not articles. πŸ™‚

Checkout eppz! Alert at

The resulting plugin interface

When designing multi-platform native Unity plugins, we come to a point where the distinct native features needs a single interface. As in the current native Unity plugin example project we simply expose native alert views with two buttons, the resulting platform-independent Unity plugin call should look something like below.

...
public void Show()
{
    // Call plugin (of whichever platform).
    plugin.ShowAlertWithAttributes(
        text.title,
        text.message,
        text.buttonTitle,
        text.cancelButtonTitle
    );
}
...

One may include the inline delegate callback as well to this method, but as I designed this to be used with UnityEnvent callbacks (resulting in a prefab can be used without coding), I don’t include callback in this method (more details later on).

Unity Android plugin class setup

Before implement the native feature, there is some housekeeping to do in the Android Studio plugin project compared to you may already have from Unity Android plugin tutorial 2. Project setup.

To accessUnity Java classes (like UnityPlayer and UnityPlayerActivity) in the Android Studio project, we should link the library to the project. Since Unity 5, the corresponding JAR files can be found at the location below (on OSX, hit ⌘+G, then paste this location).

/Applications/Unity/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes/classes.jar
To access Unity Java classes / features, the Java classes of the AndroidPlayer should be linked against the Unity Android plugin Android Studio project.

To access Unity Java classes / features, the Java classes of the AndroidPlayer should be linked against the Unity Android plugin Android Studio project.

So beside copy the classes.jar file to the libs folder of the plugin module (I also prefer to rename it to something more meaningful than classes.jar), we have to add as a dependency, but also indicate that it will be already provided in the final application (so the classes will be excluded from our compiled plugin). Just add this dependency to the plugin module Gradle file (more info on Gradle build scripts in Unity Android plugin tutorial 2. Project setup).

...
dependencies {
    provided files('./libs/UnityPlayer.jar') // Avoid redundant Unity Player in final application APK
}
...

Having this, you can use Unity Android player Java classes across your plugin source files by import com.unity3d.player.UnityPlayer; at the top. Accessing UnityPlayer static methods like currentActivity, and UnitySendMessage is essential.

After you import the library, you can see how Android Studio decompiles the Unity Android player Java files for you, so you can start inspect source of UnityPlayer, UnityPlayerActivity if feeling explorous. It results in something similar I shared at Decompiled Java sources of Unity Player for Android.

Creating Unity Android plugin class

After this, you can start create your plugin class. Unless you need specific Activity features, I strongly suggest to use Fragment based class, as it has countless benefits when developing Unity Android plugin (more on this in Unity Android plugin tutorial 1. Fundamentals). The snippet below creates a static start() method that instantiate the plugin, and attach it to the main application Activity.

Please note that I only share snippets of code hereafter, but you can always inspect the entire working source of the given files with the full context following the GitHub references I provide below mostly each snippet.

package com.eppz.plugins;

import android.app.Fragment;
import com.unity3d.player.UnityPlayer;

public class EPPZ_Alert extends Fragment
{
    // Constants.
    public static final String TAG = "EPPZ_Alert";

    // Singleton instance.
    public static EPPZ_Alert instance;

    // Unity context.
    String gameObjectName;

    public static void start(String gameObjectName)
    {
        // Instantiate and add to Unity Player Activity.
        instance = new EPPZ_Alert();
        instance.gameObjectName = gameObjectName; // Store 'GameObject' reference
        UnityPlayer.currentActivity.getFragmentManager().beginTransaction().add(instance, EPPZ_Alert.TAG).commit();
    }

...

}
See full EPPZ_Alert.java at

The static TAG constant comes handy later on when you want to resolve a reference to this Fragment from other classes (using FragmentManager.findFragmentByTag()).

The static instance field holds a reference to the instantiated plugin class itself to easily access from Unity using AndroidJavaClass.GetStatic().

Holding a note of the name of the GameObject that holds the plugin in Unity comes handy when use UnitySendMessage to send callbacks. Having this, you can also maintain multiple plugin fragments managed by multiple :GameObejcts: in your scene.

To keep the Fragment instance alive between Android configuration changes (like device rotation for example), we can indicate to retain it once created implementing onCreate() template method below.

...
@Override
public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setRetainInstance(true); // Retain between configuration changes (like device rotation)
}
...
See full EPPZ_Alert.java at

Having the above, you can nicely resolve the class, and the instance in Unity. I also prefer to make a getter for the plugin instance using AndroidJavaClass.GetStatic(), so calling methods are a single line afterwards.

public class EPPZ_Alert_Android
{

	...

	AndroidJavaClass _class;
	AndroidJavaObject instance { get { return _class.GetStatic<AndroidJavaObject>("instance"); } }

	void Setup()
	{
		_class = new AndroidJavaClass("com.eppz.plugins.EPPZ_Alert");
		_class.CallStatic("start", gameObjectName);
	}

	...

}
See full EPPZ_Alert_Android.cs at

Add features

In the Fragment class, you can just create public instance methods to provide plugin features. The single feature for this plugin is to show a native alert dialog, so it is made of a single method, and a callback provided for the alert. I won’t bother you with the details of exposing an alert here (see details in EPPZ_DialogFragment.java if interested), only the hooks toward.

...
public void showAlertWithAttributes(String title, String message, String positiveButtonTitle, String negativeButtonTitle)
{
	// Create and show dialog.
	EPPZ_DialogFragment dialogFragment ...
}

public void dialogDidFinishWithResult(EPPZ_DialogFragment dialog, String selectedButtonTitle)
{
    UnityPlayer.UnitySendMessage(gameObjectName, "AlertDidFinishWithResult", selectedButtonTitle);
}
...
See full EPPZ_Alert.java at

After you have set up the plugin instance in a Unity class that wraps up the android plugin, you can simply call this method from Unity using AndroidJavaObject.Call().

instance.Call("showAlertWithAttributes", title, message, buttonTitle, cancelButtonTitle);

The callback with the result will be sent to a GameObject with the corresponding name, implementing AlertDidFinishWithResult() we specified above. It also comes with a string parameter holding the title of the selected button.

...
public void AlertDidFinishWithResult(string selectedButtonTitle)
{
	Debug.Log("Button '"+selectedButtonTitle+"' selected.");
}
...

The script that receives the callback is not quiet necessarily the same that initiated the call. If you support multiple platforms, and extensive / developing feature set, I suggest you to split platform specific code to distinct files, and provide a unified interface that can manipulate each.

Creating common interface for platforms

If you take a look at Google Analytics plugin for Unity scripts (at the time of this writing), you can see a single GoogleAnalyticsV4.cs that instantiates different tracker classes for each platform (see line 107 for more).

The idea is really something similar, but instead of doing #if UNITY_ANDROID every time, I prefer (much) more to use a single virtual class with a static factory method, so deal with #if UNITY_ANDROID a single time, then carry out the rest of the features in distinct platform classes (including Editor fallback).

With this case of our Unity native alerts plugin, the abstract class contains a constructor hungry for a gameObjectName, and two virtual template method hooks for subclasses.

public class EPPZ_Alert
{

    ...

    protected string gameObjectName;

    public EPPZ_Alert(string gameObjectName)
    {
        this.gameObjectName = gameObjectName;
        Setup();
    }

    virtual protected void Setup() { }

    virtual public void ShowAlertWithAttributes(string title, string message, string buttonTitle, string cancelButtonTitle) { }

    ...
}
See full EPPZ_Alert.cs at

The constructor is pretty straightforward. It calls a template method Setup() where subclasses can setup their own native hooks upon creation. I prefer gameObjectName to be protected, so it won’t be exposed to external users of the class, while subclasses can reach it.

The feature ShowAlertWithAttributes() is abstracted to this form above, so every native alert will have a title, a message, a buttonTitle (the “Ok” button), and a cancelButtonTitle. There is a lot of space for customization here even for a simple alert view, but the sake of this tutorial this single method is far perfect. External users of the class can call this method directly, like I did it on the top of this article.

Now you can start implement the template methods in new subclasses per each platform. The Android implementation would look really like I outlined all above, but now you can see how it fits into this abstract class template.

public class EPPZ_Alert_Android : EPPZ_Alert
{
    

    public EPPZ_Alert_Android(string gameObjectName) : base(gameObjectName) { }


    #region Native setup

    AndroidJavaClass _class;
    AndroidJavaObject instance { get { return _class.GetStatic<AndroidJavaObject>("instance"); } }

    override protected void Setup()
    {
        // Start plugin Fragment`.
        _class = new AndroidJavaClass("com.eppz.plugins.EPPZ_Alert");
        _class.CallStatic("start", gameObjectName);
    }

    #endregion


    #region Features

    override public void ShowAlertWithAttributes(string title, string message, string buttonTitle, string cancelButtonTitle)
    {
        Debug.Log("EPPZ_Alert_Android.ShowAlertWithAttributes(`"+title+"`, `"+message+"`, `"+buttonTitle+"`, `"+cancelButtonTitle+"`, )");
        instance.Call("showAlertWithAttributes", title, message, buttonTitle, cancelButtonTitle);
    }

    // ...

    #endregion
}
See full EPPZ_Alert_Android.cs at

In the first place, it is a subclass of EPPZ_Alert, and simply inherit the constructor as it is. The Native setup region just lays the bridge between the Java class and this one, by overriding Setup() template (will be called upon creation).

The feature is really just a method call here using AndroidJavaObject.Call() using the instance getter we created in Java before.

The iOS implementation of EPPZ_Alert is nearly identical, but it passes on the :GameObejct: name reference with each call (as it uses static method calls only for now). If you inspect the full source, you can see how it implements the same class as the Android counterpart did.

...
override public void ShowAlertWithAttributes(string title, string message, string buttonTitle, string cancelButtonTitle)
{
    Debug.Log("EPPZ_Alert_iOS.ShowAlertWithAttributes(`"+title+"`, `"+message+"`, `"+buttonTitle+"`, `"+cancelButtonTitle+", )");
    _showAlertWithAttributes(
        title,
        message,
        buttonTitle,
        cancelButtonTitle,
        gameObjectName
    );
}
...
See full EPPZ_Alert_iOS.cs at

The Editor part is really just for fake native functionality (tapping “Ok” button), also to enable debug sessions during Editor Play mode. The feature actually “mimics” the effect of a native UnitySendMessage to the given GameObject.

...
override public void ShowAlertWithAttributes(string title, string message, string buttonTitle, string cancelButtonTitle)
{ GameObject.Find(gameObjectName).SendMessage("AlertDidFinishWithResult", buttonTitle); }
...

So once we have every implementation in different classes, we can construct the factory method in the abstract class. A simple static method, that decides what class we should use depending on the current platform setup.

public class EPPZ_Alert
{

    ...

    public static EPPZ_Alert pluginWithGameObjectName(string gameObjectName)
    {
        EPPZ_Alert plugin;

        // Get plugin class for the actual platform.
        #if UNITY_IPHONE
        plugin = (Application.isEditor)
            ? (EPPZ_Alert)new EPPZ_Alert_Editor(gameObjectName)
            : (EPPZ_Alert)new EPPZ_Alert_iOS(gameObjectName);
        #elif UNITY_ANDROID
        plugin = (Application.isEditor)
            ? (EPPZ_Alert)new EPPZ_Alert_Editor(gameObjectName)
            : (EPPZ_Alert)new EPPZ_Alert_Android(gameObjectName);
        #endif

        return plugin;
    }

    ...

}
See full EPPZ_Alert.cs at

While it is a great advantage on its own, to maintain the implementations for different platforms in different files, cutting down dozens of #if statements and other clutters, the real benefit shows when you areusing this plugin class as a client.

Using common plugin interface

In this example I decided to create a Prefab based alert view using UnityEvents. Having this, the users of the plugin can have it up and running without coding (knowing it is preferred in the Unity Asset Store), it is completely matter of taste however. The usage of our plugin class will carried out within this single Alert.cs script.

Unity_Android_Plugin_Tutorial_Alert_Prefab_Setup

The labels can be set in the Inspector, then the alert can be shown calling a public Show() method on the prefab (hooked up to the On Click () event of the Button), and triggers the callback event Alert Did Finish With Result (String), that contains a dynamic String can be directly set to a Text instance.

...
private EPPZ_Alert plugin;

void Start()
{
    // Create plugin instance (of whichever platform).
    plugin = EPPZ_Alert.pluginWithGameObjectName(gameObject.name);
}
...
public void Show()
{
    // Call plugin (of whichever platform).
    plugin.ShowAlertWithAttributes(
        text.title,
        text.message,
        text.buttonTitle,
        text.cancelButtonTitle
    );
}
...
public void AlertDidFinishWithResult(string selectedButtonTitle)
{
    // Trigger event.
    alertDidFinishWithResult.Invoke(selectedButtonTitle);
}
...
See full Alert.cs at

For using the plugin, first you have to ask for an instance from the factory, then later on use this interface to reach out for the native feature we already know.

As the plugin was initialized with the name of this GameObject, the plugin callback AlertDidFinishWithResult() should also implemented here.

From here

That’s it, congrats to carry through these articles! I wish I could give you some kind of badge or so. πŸ˜€ If you think others may find this useful, you may share this :Unity Android plugin tutorial series:. I have setup some nicely laid out twitter cards in <meta>.

I think this arrangement of classes is good for most cases, establish a solid base for extending functionality. There are many points however, where this could be improved, especially in terms of communication. You could use an interface to ensure that the callback methods are implemented at least, orskip UnitySendMessage altogether, and replace it with direct class-to-class callbacks using mono_runtime_invoke() or even better Mono thunks. As it seems now, the upcoming Unity iOS plugin tutorial series will pretty much cover further details on this.

DISCLAIMER. THE INFORMATION ON THIS BLOG (INCLUDING BUT NOT LIMITED TO ARTICLES, IMAGES, CODE SNIPPETS, SOURCE CODES, EXAMPLE PROJECTS, ETC.) IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE INFORMATION ON THIS BLOG (INCLUDING BUT NOT LIMITED TO ARTICLES, IMAGES, CODE SNIPPETS, SOURCE CODES, EXAMPLE PROJECTS, ETC.).

  • Luis Ortegano

    Excellent tutorial!, is a good guide to do a pretty good plugin.

    now i wonder if is any posibility of display a FragmentWebview on unity to display local files, in my case an html. if you know how to do it let me know plaese!

    Grettings!

  • 김진희

    I think your document is good for the beginner, thanks a lot.

    I have a question related with this article, I’d happy to see your reply.
    I reviewed all your sample project, and I made my custom library. It works well.

    The goal of making a unity android plugin is import external SDK and make my custom plugin.
    The external SDK is for specific company’s beacon SDK, they serve .arr format module.

    I added classes.jar and external module file like this on build.gradle

    dependencies {
    provided files(‘./libs/UnityPlayer.jar’)
    compile project(‘:sample-sdk-android’)
    }

    and then generate .arr file. so far so good !

    but when I run it through Unity side there is an error.
    java.lang.ClassNotFoundException: com.jinhee.recolibrary.BeaconServiceManager

    The android project works well before I added external module.
    Can you give me an idea and way to add another module to my module.
    please help me !

    • You said you added classes.jar to the project, so why don’t just compile('classes.jar')?

      Or simply find com.jinhee.recolibrary in the SDK sample project, then link against manually. Furthermore, you can always see the resulting Android APK internals in StagingArea folder in Unity project folder, so you can inspect which classes has / has not included in the final build.

  • Warp

    Hey! Thank you very much for this tutorial! Took a fair bit of searching to finally find it and get things up and running. I was desperately searching for a way to generate thumbnail images for videos on Android devices. Thanks to your tutorial and a little copy-pasta from another useful snippet online I just got it working. Without thinking, for my initial test, I used the movie ‘Gravity’ which I had on my phone. The result was this appropriate image that let me know everything was working! πŸ™‚

    • eppz!

      Thanks, glad you made a good use of it! πŸ™Œ

  • GarethNewtquest

    Brilliant tutorial series. Detailed, easy to follow and a great solution at the end of it. Nothing else like it. Looking forward to the iOS tutorial.

  • Antao Almada

    Thanks a lot for this tutorial. It’s very helpful. The use of a Fragment is ingenious.
    I have an issue regarding EPPZ_Alert Java implementation as a singleton. I assume you can have several instances of the alert dialog. Each attached to a different GameObject. Each instance calls pluginWithGameObjectName that adds a Fragment to the current Unity Activity. Meanwhile, the instance member variable is static so it can only keep a references to the latest instance of EPPZ_Alert.
    My suggestion is to get rid of instance and change start() to return EPPZ_Alert. The managed class keeps the returned instance as an AndroidJavaObject.
    Am I wrong?

    • Yap, that’s absolutely true for Android, but iOS plugin counterpart cannot give back class instances that easily (it can be done using Mono runtime inspection). I already started to outline a library called EPPZ_iOS_Objects, but probably I won’t finish it up until I really need it in production.

      • Antao Almada

        Ok! I’m working on a Android-only plugin so I didn’t notice the iOS issue.

        • Yap, I’ll work out EPPZ_iOS_Objects for sure, but now I’m focusing on ship tangram! first.

  • thanks you. share a beutiful tutorial.

    • No worris, mai plesur. πŸ˜€

      • I found another method about callback.

        check this if you want AOT.MonoPInvokeCallbackAttribute.

        • Yap, I have to use Mono directly anyway to do something similar on iOS.

  • GuavamanX

    Thanks for the tutorial! It’s the most thoughtfully-written, detailed tutorial I’ve ever seen on this kind of subject. I did have a problem with the dependencies step. Using the “provided files” method does not work in Android Studio 2.2.2 when building a library. The JAR is always included in the final library build which causes it to throw exceptions when Unity tries to convert to Dex format because the Unity classes are included twice. I finally came across a method that worked that involves creating a second library from the main project, copying the library JAR into that 2nd library’s libs folder, and including the 2nd library as a dependency of the 1st library. The final built AAR does not include the Unity classes.jar and works. This is an awfully roundabout method though. Is there anything else missing from the tutorial that might make it work with the “provided files” method? FYI, the answer in this link shows how to set up the 2nd library in the project for those having the same problems: http://stackoverflow.com/questions/26576635/how-to-exclude-a-jar-file-from-my-android-library-build-target

    • eppz!

      Thanks for the link!
      I was using Android Studio 2.0 for the article, and it managed to skip Unity libraries… πŸ€”

    • Weijia Yu

      Mine worked fine. Maybe you didn’t clean and rebuild your projects after modifying to “provided”? Including jar file should be done in build process. Maybe after you add another module, it rebuilt or built from fresh and thats why you got it working.

  • Nika Kasradze

    I also had that java.lang.ClassNotFoundException. I spent around 9 hours trying to figure out what was wrong. In the end this turned out to be an incorrect android class name passed to Unity’s AndroidJavaClass constructor. This is what solved it for me:
    1. use an archive extractor software (winrar, 7zip, etc…) to extract .aar file in the same folder.
    2. you will get some folders, AndroidManifest.xml and classes.jar file extracted
    3. extract classes.jar file in the same folder and go open the extracted “classes” folder
    4. the folder structure probably goes like classes > com > yoCompany > yoPackage
    5. in yoPackage there are some .class files
    6. you can use a decompiler to decompile those file into a readable code, or if you are lazy like me just open it with notepad (or whatever text editor)
    7. you’ll see some yada yada unreadable characters BUT there should be some readable ones as well, like you should see “com/yoCompany/yoPackage/yoClass” somewhere towards the beginning of the file
    8. just replace the “/” character with “.” and that’s your EXACT classname you should use in Unity’s AndroidJavaClass constructor

    • eppz!

      Thanks for sharing the process!
      Note that you can always see the resulting Android APK internals in StagingArea folder in Unity project folder, where you can inspect every class that has been added to the APK

  • Carina

    This works wonderfully well compared to extending the UnityPlayerActivity, thank you for saving my sanity.

  • saulo

    Hi, very good, thanks for the tutorial !!!

    I have a doubt.

    I have tried to return the wifi signal (int) for unity, but the function
    WifiManager and getSystemService (Context.WIFI_SERVICE);

    Signal = wifiInfo.getRssi ();

    Does not seem to work inside the plugin.
    Does not return any value for the app built in unity

    The plugin works without these functions, passing values ​​of variables normally.

    Anyone have any help?
    πŸ™‚
    (sorry the English).

  • Fatih Turker

    Very informative step by step article. Is it possible use other than DialogFragment to view on top of unity app? I tried Fragment with layout.xml by setting background color to transparent but did not work, nothing shows up. I think we need to reach unity’s currentactivity View or ViewGrop to get View id in order show ?

    • While I cant really understand what you are about to achieve, but it sounds you are on the right track. Use Android Device Monitor to debug your views, like you can see it in Unity Android plugin tutorial (1/3) Fundamentals.

      • Fatih Turker

        I was trying to go with your way of doing plugin when implementing my plugin but i found strange behaviour when you pause and resume the app. Unity screen is white except the my fragment. I spend few days to debug nothing solves the problem . So i decide to try your example https://github.com/eppz/Unity.Blog.Unity_Android_plugin_tutorial . I found that it does too. When activity got resumend i think we should invalidate fragment background. I suspect that it occurs because of SurfaceView rendering! ?

  • Jorge Luis Romano

    Thanks a lot for this tutorial. I implemented my plugin with this approuch but I depared with a doub:
    – How about overrid onActivityResult on Fragments?
    I tried use it in the Fragment, but the code is not called.

    • onActivityResult is an Activity template method indeed, it doesn’t propagate to Fragment classes AFAIK.

      I had similar issues when I was about to saving photos to library, so I had to rescan the media on the device. I hooked up my callback invocations with inline listeners instead using onActivityResult, something like below.


      MediaScannerConnection.scanFile(
      _unityPlayerActivity,
      new String[] { externalImagePath }, null,
      new MediaScannerConnection.OnScanCompletedListener()
      {
      public void onScanCompleted(String path, Uri uri)
      { SendUnityMessage(SAVE_TO_GALLERY_CALLBACK_METHOD_NAME, SAVED); }
      });

      I don’t know what is your use case, probably it worth a research on the available API options to work around onActivityResult.

  • Mohit Pundir

    Hii thanx for the amazing workflow. I am getting the following error when building the apk from unity.

    Seems like a duplicate issue because of the UnityPlayer.jar. How do I resolve this?

    CommandInvokationFailure: Unable to convert classes into dex format.
    /Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/bin/java -Xmx2048M -Dcom.android.sdkmanager.toolsdir=”/Users/mohitpundir/Library/Developer/Xamarin/android-sdk-macosx/tools” -Dfile.encoding=UTF8 -jar “/Applications/Unity/PlaybackEngines/AndroidPlayer/Tools/sdktools.jar” –

    stderr[
    Uncaught translation error: java.lang.IllegalArgumentException: already added: Lbitter/jnibridge/JNIBridge;
    Uncaught translation error: java.lang.IllegalArgumentException: already added: Lbitter/jnibridge/JNIBridge$a;
    Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/unity3d/player/GoogleVrProxy;
    Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/unity3d/player/GoogleVrProxy$1;
    Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/unity3d/player/GoogleVrProxy$2;
    Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/unity3d/player/GoogleVrProxy$3;
    Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/unity3d/player/GoogleVrProxy$4;
    Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/unity3d/player/GoogleVrProxy$a;
    Uncaught translation error: java.lang.IllegalArgumentException: already added: Lcom/unity3d/player/NativeLoader;

    • Hello, thanks for the words! πŸ™Œ
      Probably you have the plugin project within the Assets folder as well. But I recommend to debug the issue step-by-step. You can always lookup the build APK content in the Temp/StagingArea folder.

      • Mohit Pundir

        Thanks. As you said, the issued seemed to be with adding the jar dependencies. I re-imported the unity classes.jar and everything is working as expected.

        • Happy to hear!
          Hope this issue help others as well, thanks for sharing.

      • Jiri Dr

        I have the same problem (duplicate issue), but cannot find right solution. Inside my build.gradle file I have:
        dependencies {
        compile fileTree(include: [‘*.jar’], dir: ‘libs’)
        androidTestCompile(‘com.android.support.test.espresso:espresso-core:2.2.2’, {
        exclude group: ‘com.android.support’, module: ‘support-annotations’
        })
        compile ‘com.android.support:appcompat-v7:26.+’
        testCompile ‘junit:junit:4.12’
        compile ‘com.google.protobuf:protobuf-java:2.6.1’
        provided files(‘./libs/UnityPlayer.jar’)
        }
        where “UnityPlayer.jar” is “classes.jar” from Unity. Final .aar library from the Android Studio is inside “Assets/Plugins/Android” Unity folder. Any ideas how to solve the problem? Thx

        • Well, actually the answer is right in the article above. I thought you read it before comment on any issue. πŸ€”

          https://uploads.disquscdn.com/images/9cce23560f46a210c79b070a5873b435afa1527402516562588af5a723b36060.jpg

          “we have to add as a dependency, but also indicate that it will be already provided in the final application (so the classes will be excluded from our compiled plugin)”

          Use provided directive.

          In your special case, probably this line compile fileTree(include: ['*.jar'], dir: 'libs') links every .jar files in libs.

          • Jiri Dr

            I read all previous comments, but without finding solution. I have provided directive inside build.gradle but with the same error result (as I wrote in my comment πŸ˜‰ ). The solution for me is maybe removing compile fileTree from build.gradle.

            Your last post-sentence is very special for me, thanks very much.

          • Sure thing! πŸ™

  • 5argon

    Thanks for the “indicate that it will be already provided in the final application”… saved my life..
    The official docs just say “add the jar!” and ofc when I use the compiled plugin I got a lot of errors. lol

    • Happy to hear!
      I had hard times as well, when I first time debugged it.
      It involved some Android / Gradle only research on its own.

      • 5argon

        Thanks for the whole tutorial! Currently I am wondering if it is possible to use both Fragment way and also receive touch event from the activity we attached to? (Or a touch of UnityPlayer view inside it)

        • Well, whatever you are about to create, this sounds somewhat redundant.

          Unity is capable of handling touches. You should make your UI layer (so the user interactions as well) multi-platform, right in Unity. Put the service code only into plugins. Put only things that varies platform by platform into the plugins. Touches are not that kind of thing.

          On the other hand, if you are targeting Android only, then make a native Application, leave Unity altogether.

      • 5argon

        Thanks for the whole tutorial! Currently I am wondering if it is possible to use the Fragment way and also receive touch event from the activity we attached to? (Or a touch of UnityPlayer view inside it)