hello guys i am propos ing a new extension that can be very usefull its about rive see rive is an animation based file that could do a lot of stuff See Here So the only way to execute these rive files (.riv) is through webveiwer in MIT App Inventor but the problem is the web veivwer based runtime is smaller and not so flexible and the android runtime extension needs to be added to have more power but its only for native language but buy creating an extension in kotlin for mit app inventor this can be resolved and am working on it too so chek hereandroid runtime and this my basic structure of code that may need some changes :- `package com.example.riveextension;
import android.content.Context;
import android.view.View;
import android.util.Log;
import com.google.appinventor.components.annotations.*;
import com.google.appinventor.components.runtime.*;
import java.io.InputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.OutputStream;
// Rive imports – adjust according to the actual rive-android dependency
// import app.rive.runtime.kotlin.RiveAnimationView;
// import app.rive.runtime.kotlin.core.RiveEvent; // Example, actual class name may differ
@DesignerComponent(
version = 1,
description = "Rive interactive animation view with Firebase backend logging",
category = ComponentCategory.ANIMATION,
nonVisible = false,
iconName = "aiwebres/icon.png"
)
@SimpleObject(external = true)
@UsesLibraries(libraries = "rive-android.aar") // adjust as needed
public class RiveInteractiveView extends AndroidViewComponent {
private final Context context;
private final ComponentContainer container;
// Replace View with actual RiveAnimationView type
private View riveView;
private String source = "";
private String artboard = "";
private String stateMachine = "";
private String firebaseUrl = "";
private String firebasePath = "/events";
private String firebaseAuthToken = "";
public RiveInteractiveView(ComponentContainer container) {
super(container);
this.container = container;
this.context = container.$context();
// TODO: Replace with actual RiveAnimationView creation
// riveView = new RiveAnimationView(context);
riveView = new View(context); // placeholder to keep structure valid
// Add view to container
container.$add(this);
// TODO: set up Rive listeners here when you have real RiveAnimationView
// setupRiveListeners();
}
@Override
public View getView() {
return riveView;
}
// ========= Rive Properties ===========
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "")
@SimpleProperty(description = "Rive .riv asset file name, e.g. game.riv")
public void Source(String value) {
source = value;
loadRive();
}
@SimpleProperty(description = "Rive .riv asset file name")
public String Source() {
return source;
}
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "")
@SimpleProperty(description = "Artboard name inside the Rive file")
public void Artboard(String value) {
artboard = value;
loadRive();
}
@SimpleProperty
public String Artboard() {
return artboard;
}
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "")
@SimpleProperty(description = "State machine name inside the Rive file")
public void StateMachine(String value) {
stateMachine = value;
loadRive();
}
@SimpleProperty
public String StateMachine() {
return stateMachine;
}
// ========= Firebase Properties ===========
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "")
@SimpleProperty(description = "Firebase Realtime DB base URL (without .json). Example: https://your-project-id.firebaseio.com")
public void FirebaseUrl(String value) {
firebaseUrl = value;
}
@SimpleProperty
public String FirebaseUrl() {
return firebaseUrl;
}
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "/events")
@SimpleProperty(description = "Path in Firebase where events will be logged, e.g. /events or /users/user123/events")
public void FirebasePath(String value) {
firebasePath = value;
}
@SimpleProperty
public String FirebasePath() {
return firebasePath;
}
@DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_STRING, defaultValue = "")
@SimpleProperty(description = "Firebase auth token / ID token for REST (optional)")
public void FirebaseAuthToken(String value) {
firebaseAuthToken = value;
}
@SimpleProperty
public String FirebaseAuthToken() {
return firebaseAuthToken;
}
// ========= Events ===========
@SimpleEvent(description = "Fired when a Rive event is emitted from the state machine.")
public void RiveEvent(String eventName, String dataText) {
EventDispatcher.dispatchEvent(this, "RiveEvent", eventName, dataText);
}
// ========= Public Functions (optional) ===========
@SimpleFunction(description = "Reload the Rive animation.")
public void Reload() {
loadRive();
}
// ========= Internal Rive Loading Logic ===========
private void loadRive() {
if (source == null || source.isEmpty()) return;
// TODO: Use actual Rive APIs – this part is pseudocode.
// Example-ish logic:
//
// RiveAnimationView realView = (RiveAnimationView) riveView;
// realView.setRiveResource(source, artboard, stateMachine);
// realView.play();
//
// Make sure to call setupRiveListeners() after loading.
Log.d("RiveInteractiveView", "loadRive called with: " + source + ", artboard=" + artboard + ", sm=" + stateMachine);
}
// ========= Rive Event Listener Hook ===========
private void setupRiveListeners() {
// PSEUDOCODE – adjust to real Rive API
//
// RiveAnimationView realView = (RiveAnimationView) riveView;
// realView.setEventListener(new RiveEventListener() {
// @Override
// public void onEvent(RiveEvent event) {
// String name = event.getName();
// String data = event.getDataJsonString(); // or build JSON from fields
// handleRiveEvent(name, data);
// }
// });
}
private void handleRiveEvent(final String eventName, final String dataText) {
// 1) Fire App Inventor event
RiveEvent(eventName, dataText);
// 2) Send to Firebase
if (firebaseUrl != null && !firebaseUrl.isEmpty()) {
new Thread(new Runnable() {
@Override
public void run() {
sendEventToFirebase(eventName, dataText);
}
}).start();
}
}
// ========= Firebase REST Logic ===========
private void sendEventToFirebase(String eventName, String dataText) {
try {
// Build URL: {firebaseUrl}{firebasePath}.json?auth=token
String path = firebasePath;
if (!path.startsWith("/")) {
path = "/" + path;
}
StringBuilder urlBuilder = new StringBuilder();
urlBuilder.append(firebaseUrl);
if (firebaseUrl.endsWith("/")) {
// nothing
} else {
urlBuilder.append("/");
}
urlBuilder.append(path.substring(1)); // remove leading slash
if (!urlBuilder.toString().endsWith(".json")) {
urlBuilder.append(".json");
}
if (firebaseAuthToken != null && !firebaseAuthToken.isEmpty()) {
urlBuilder.append("?auth=").append(firebaseAuthToken);
}
URL url = new URL(urlBuilder.toString());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST"); // push new event
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
conn.setDoOutput(true);
// Basic JSON body: { "eventName": "...", "data": "...", "timestamp": 123456 }
String jsonBody = "{"
+ "\"eventName\":\"" + escapeJson(eventName) + "\","
+ "\"data\":\"" + escapeJson(dataText) + "\","
+ "\"timestamp\":" + System.currentTimeMillis()
+ "}";
OutputStream os = conn.getOutputStream();
os.write(jsonBody.getBytes("UTF-8"));
os.flush();
os.close();
int responseCode = conn.getResponseCode();
Log.d("RiveInteractiveView", "Firebase response: " + responseCode);
conn.disconnect();
} catch (Exception e) {
Log.e("RiveInteractiveView", "Error sending event to Firebase", e);
}
}
private String escapeJson(String s) {
if (s == null) return "";
return s.replace("\\", "\\\\").replace("\"", "\\\"");
}
}
another alternative can be Custom webveiw by - vknow360 @ (CustomWebView : An extended form of Web Viewer)