Building Capacitor plugins for Android involves writing Java or Kotlin to interface with Android SDKs.
To get started, first generate a plugin as shown in the Getting Started section of the Plugin guide.
Next, open
your-plugin/android/
in Android Studio. You then want to navigate to the
.java
file for your plugin, which changes depending on the Plugin ID and Plugin Class Name you used when creating the plugin.
For example, for a plugin with the ID
com.domain.myplugin
and the Plugin Class Name
MyPlugin
, you would find the
.java
file at
android/src/main/java/com/domain/myplugin/MyPlugin.java
.
Capacitor uses Java by default but you can use Kotlin instead, if you prefer.
After generating a plugin, right click the Java plugin class in Android Studio and select the “Convert Java file to Kotlin file” option from the menu. Android Studio will walk you through configuring the project for Kotlin support. Once this is completed, right click the Java class again and re-select the conversion option to convert it to a Kotlin class.
A Capacitor plugin for Android is a simple Java class that extends
com.getcapacitor.Plugin
and have a @NativePlugin
annotation.
It has some methods with
@PluginMethod()
annotation that will be callable from JavaScript.
Once your plugin is generated, you can start editing it by opening the file with the Plugin class name you choose on the generator.
In the generated example, there is a simple echo plugin with an
echo
function that simply returns a value that it was given.
This example demonstrates a couple core components of Capacitor plugins: receiving data from a Plugin Call, and returning data back to the caller.
EchoPlugin.java
package android.plugin.test;
import com.getcapacitor.JSObject;
import com.getcapacitor.NativePlugin;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
@NativePlugin()
public class EchoPlugin extends Plugin {
public void load() {
// Called when the plugin is first constructed in the bridge
}
@PluginMethod()
public void echo(PluginCall call) {
String value = call.getString("value");
JSObject ret = new JSObject();
ret.put("value", value);
call.success(ret);
}
}
In order to make Capacitor aware of your plugin, you have to export it to capacitor in your apps
MainActivity
.
If choosing to use Kotlin instead of Java, the Echo plugin example looks like this:
EchoPlugin.kt
package android.plugin.test;
import com.getcapacitor.JSObject;
import com.getcapacitor.NativePlugin;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
@NativePlugin()
class EchoPlugin : Plugin() {
@PluginMethod
fun echo(call: PluginCall) {
val value = call.getString("value")
val ret = JSObject()
ret.put("value", value)
call.success(ret)
}
}
In order to make Capacitor aware of your plugin, you have to export it to capacitor in your apps
MainActivity
.
It is recommended for Kotlin files to be in the
android/src/main/java/
directory where Java files might also reside.
Each plugin method receives an instance of
com.getcapacitor.PluginCall
containing all the information of the plugin method invocation from the client.
A client can send any data that can be JSON serialized, such as numbers, text, booleans, objects, and arrays. This data
is accessible on the
getData
field of the call instance, or by using convenience methods such as
getString
or getObject
.
For example, here is how you’d get data passed to your method:
@PluginMethod()
public void storeContact(PluginCall call) {
String name = call.getString("yourName", "default name");
JSObject address = call.getObject("address", new JSObject());
boolean isAwesome = call.getBoolean("isAwesome", false);
if (!call.getData().has("id")) {
call.reject("Must provide an id");
return;
}
// ...
call.resolve();
}
Notice the various ways data can be accessed on the
PluginCall
instance, including how to check for a key using
getData
‘s
has
method.
A plugin call can succeed or fail. For calls using promises (most common), succeeding corresponds to calling
resolve
on the Promise, and failure calling
reject
. For those using callbacks, a succeeding will call the success callback or the error callback if failing.
The
resolve
method of
PluginCall
takes a
JSObject
and supports JSON-serializable data types. Here’s an example of returning data back to the client:
JSObject ret = new JSObject();
ret.put("added", true);
JSObject info = new JSObject();
info.put("id", "unique-id-1234");
ret.put("info", info);
call.resolve(ret);
To fail, or reject a call, use call.reject
, passing an error string and (optionally) an
Exception
instance
call.reject(exception.getLocalizedMessage(), exception);
Plugins can override the load
method to run some code when the plugin is first initialized:
public class MyPlugin extends Plugin {
public void load() {
}
}
To present a Native Screen over the Capacitor screen we will use Android’s Intents. Intents allow you to start an activity from your app, or from another app. See Common Intents
Most times you just want to present the native Activity, in this case you can just trigger the relevant action.
Intent intent = new Intent(Intent.ACTION_VIEW);
getActivity().startActivity(intent);
Sometimes when you launch an Intent, you expect some result back. In that case you want to use
startActivityForResult
.
Also make sure you call
saveCall(call);
as you will need it later when handling the intents result.
You also have to register your intents
unique request code with
@NativePlugin
in order for
handleOnActivityResult
to be triggered.
@NativePlugin(
requestCodes={MyPlugin.REQUEST_IMAGE_PICK} // register request code(s) for intent results
)
class ImagePicker extends Plugin {
protected static final int REQUEST_IMAGE_PICK = 12345; // Unique request code
@PluginMethod()
public void pickImage(PluginCall call) {
saveCall(call);
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
startActivityForResult(call, intent, REQUEST_IMAGE_PICK);
}
// in order to handle the intents result, you have to @Override handleOnActivityResult
@Override
protected void handleOnActivityResult(int requestCode, int resultCode, Intent data) {
super.handleOnActivityResult(requestCode, resultCode, data);
// Get the previously saved call
PluginCall savedCall = getSavedCall();
if (savedCall == null) {
return;
}
if (requestCode == REQUEST_IMAGE_PICK) {
// Do something with the data
}
}
}
Capacitor Plugins can emit App events and Plugin events
App Events are regular javascript events, like
window
or
document
events.
Capacitor provides all this functions to fire events:
//If you want to provide the target
bridge.triggerJSEvent("myCustomEvent", "window");
bridge.triggerJSEvent("myCustomEvent", "document", "{ 'dataKey': 'dataValue' }");
// Window Events
bridge.triggerWindowJSEvent("myCustomEvent");
bridge.triggerWindowJSEvent("myCustomEvent", "{ 'dataKey': 'dataValue' }");
// Document events
bridge.triggerDocumentJSEvent("myCustomEvent");
bridge.triggerDocumentJSEvent("myCustomEvent", "{ 'dataKey': 'dataValue' }");
And to listen for it, just use regular javascript:
window.addEventListener('myCustomEvent', function () {
console.log('myCustomEvent was fired');
});
Plugins can emit their own events that you can listen by attaching a listener to the plugin Object like this:
Plugins.MyPlugin.addListener('myPluginEvent', (info: any) => {
console.log('myPluginEvent was fired');
});
To emit the event from the Java plugin class you can do it like this:
JSObject ret = new JSObject();
ret.put("value", "some value");
notifyListeners("myPluginEvent", ret);
To remove a listener from the plugin object:
const myPluginEventListener = Plugins.MyPlugin.addListener(
'myPluginEvent',
(info: any) => {
console.log('myPluginEvent was fired');
},
);
myPluginEventListener.remove();
Some Plugins will require you to request permissions. Capacitor provides some helpers to do that.
First declare your plugin permissions in the
@NativePlugin
annotation
@NativePlugin(
permissions={
Manifest.permission.ACCESS_NETWORK_STATE
}
)
You can check if all the required permissions has been granted with
hasRequiredPermissions()
.
You can request all permissions with
pluginRequestAllPermissions();
.
You can request for a single permission with
pluginRequestPermission(Manifest.permission.CAMERA, 12345);
Or you can request a group of permissions with:
static final int REQUEST_IMAGE_CAPTURE = 12345;
pluginRequestPermissions(new String[] {
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
}, REQUEST_IMAGE_CAPTURE);
To handle the permission request you have to Override handleRequestPermissionsResult
@Override
protected void handleRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.handleRequestPermissionsResult(requestCode, permissions, grantResults);
log("handling request perms result");
PluginCall savedCall = getSavedCall();
if (savedCall == null) {
log("No stored plugin call for permissions request result");
return;
}
for(int result : grantResults) {
if (result == PackageManager.PERMISSION_DENIED) {
savedCall.error("User denied permission");
return;
}
}
if (requestCode == REQUEST_IMAGE_CAPTURE) {
// We got the permission
}
}
Capacitor plugins can override the webview navigation. For that the plugin can override
public Boolean shouldOverrideLoad(Uri url)
method.
Returning
true
causes the WebView to abort loading the URL.
Returning
false
causes the WebView to continue loading the URL.
Returning
null
will defer to the default Capacitor policy.
By using the
@NativePlugin
and
@PluginMethod()
annotations in your plugins, you make them available to Capacitor, but you still need an extra step in your application to make Capacitor aware of the plugins.
This is done in your apps
MainActivity
, where you
add
it in e.g.
src/main/java/com/example/myapp/MainActivity.java
like so:
// Other imports...
import com.example.myapp.EchoPlugin;
public class MainActivity extends BridgeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Initializes the Bridge
this.init(savedInstanceState, new ArrayList<Class<? extends Plugin>>() {{
// Additional plugins you've installed go here
// Ex: add(TotallyAwesomePlugin.class);
add(EchoPlugin.class);
}});
}
}