How to make your own addons!
Addons are for desktop and Android, but how addons are made for each platform are very different. Let's discuss.
On Desktop
An addon is actually just a regular JavaScript file, but it must be saved with the .simpliplay
extension (mostly for security purposes).
Ideally it should not contact remote servers (though if necessary it's fine), and it should NOT be malicious. If it is malicious, we won't add it to the app no matter how many pull requests you send.
Addons do have a few rules though:
- It cannot use Node.js, period. Node.js integration will not be enabled in the desktop app for security purposes. If you are thinking about adding features via Node.js, consider creating your own version of the app.
- We strongly recommend it works fully offline, and doesn't contact remote servers. But if it does, make sure the server stays up and mark the addon as Online.
- It should work out of the box. Users should not have to install tools so your addon will work properly. Again, if you're thinking about adding native features (like maybe a custom version of FFmpeg), consider creating your own version of the app.
- If your addon is susceptible to memory leaks, we heavily recommend adding the drop-in janitor below. You (usually) won't need to modify it, you can simply copy-paste it at the top of your script and go.
Adding addons to the app
If you want, you can make a pull request to the official SimpliPlay Desktop repository. It should state what the addon does, and should follow the rules above. If it doesn't we may reject your addon. If we don't find it to be useful, we may also reject it. But don't feel bad about it! We just want to ensure users want such a feature. If it is something like maybe an addon that changes the look and feel of the app on the user's command or adds playlist support, we'll gladly add it without turning back!
Janitor
The Janitor is a drop-in cleanup tool that helps prevent memory leaks, event listeners and stuff lingering when you reload your addon, etc.
(function () {
// Use a global undo stack to prevent redeclaration
// name the undostack uniquely so other addons' undo stacks don't conflict
window.yourAddonUndoStack = window.yourAddonUndoStack || [];
var undoStack = window.yourAddonUndoStack; // we use var because it can be reassigned at any time - otherwise it would fail with an already declared error if we loaded the addon a second time - yeah let and const are better but we had no choice really
var myCoolVariable = "i am sort of cool";
// Later in your code, you change it:
myCoolVariable = "totally different now";
// Save an undo step:
undoStack.push(() => {
myCoolVariable = "i am super cool";
});
// Observe script removal
var script = document.currentScript;
if (script && script.parentNode) {
var observer = new MutationObserver(function (mutations) {
for (var m of mutations) {
for (var node of m.removedNodes) {
if (node === script) {
for (var i = undoStack.length - 1; i >= 0; i--) {
try { undoStack[i](); } catch (e) {} // this is what executes whatever is in the undoStack
}
observer.disconnect();
}
}
}
});
observer.observe(script.parentNode, { childList: true });
}
})();
Yes, it definitely looks a bit complex, but it makes sure any memory leaks will be gone, and if it doesn't do it, you can easily add memory leak handling before the observer.disconnect()
that finishes off any potential memory leaks before they happen.
You can also add your own undo conditions, like:
var myCoolVariable = "i am sort of cool";
// Later in your code, you change it:
myCoolVariable = "totally different now";
// Save an undo step:
undoStack.push(() => {
myCoolVariable = "i am super cool";
});
On Android
On Android, things are a bit different. First, download the source code for whichever SimpliPlay version you want. Then, you'll need to create an .aix extension. The easiest way to do this is the Niotron IDE (don't worry, its extension outputs work in MIT App Inventor as well), but you can also use the Rush CLI, etc. You'll program this custom .aix file in Java (or Kotlin if you choose). A Java example for an addon is:
package org.anirudhsevugan.simpliplaysocialviewer;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import com.google.appinventor.components.annotations.*;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.runtime.AndroidNonvisibleComponent;
import com.google.appinventor.components.runtime.ComponentContainer;
import com.google.appinventor.components.runtime.EventDispatcher;
@DesignerComponent(
version = 1,
description = "When social media URLs are entered in the media URL field, it opens it in a browser. Meant for the SimpliPlay Android app (https://simpliplay.netlify.app)",
category = ComponentCategory.EXTENSION,
nonVisible = true,
iconName = "")
@SimpleObject(external = true)
//Libraries
@UsesLibraries(libraries = "")
//Permissions
@UsesPermissions(permissionNames = "")
public class SimpliPlaySV extends AndroidNonvisibleComponent {
//Activity and Context
private Context context;
private Activity activity;
public SimpliPlaySV(ComponentContainer container){
super(container.$form());
this.activity = container.$context();
this.context = container.$context();
}
@SimpleFunction(description = "Sample Function - use this to test if functions work")
public void TestFunction(){
// Optional placeholder function
}
@SimpleEvent(description = "Sample Test Event - use this to test if events work")
public void TestEvent(){
EventDispatcher.dispatchEvent(this, "TestEvent");
}
@SimpleFunction(description = "Opens a URL in a browser if it contains social media sites like vimeo, facebook, instagram, twitter, x, reddit, youtube, etc")
public void OpenSocialMediaURL(String url) {
if (url != null && containsValidSite(url)) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
activity.startActivity(intent);
} catch (Exception e) {
OpenFailed(url);
}
}
}
private boolean containsValidSite(String url) {
String[] sites = {
"vimeo.com",
"facebook.com",
"instagram.com",
"twitter.com",
"x.com",
"reddit.com",
"youtube.com",
"discord.com",
"discord.gg",
"tiktok.com"
};
url = url.toLowerCase();
for (String site : sites) {
if (url.contains(site)) return true;
}
return false;
}
@SimpleEvent(description = "Triggered if opening the URL fails")
public void OpenFailed(String url) {
EventDispatcher.dispatchEvent(this, "OpenFailed", url);
}
}
After you've compiled the .aix file, open the App Inventor workspace, then follow the steps to import SimpliPlay's source code if you haven't already. After that, scroll down to the Extensions tab, click it, click Import Extension, then select the .aix file you created, then once the extension has loaded, drag it onto the imaginary phone in the editor. Then, go into the blocks editor, use the extension's blocks of code to activate its functionality, recompile the app, and ta-da! You have your addon!
I know this seems a bit crude, but it's really the best way we can do this; Native apps in general do not support adding code in realtime, and in an environment like AI2, this becomes even harder, so this is the best way for now.
Thanks for your support, and enjoy the addons system!