I'm testing Rust with JNI async execution. I want to do execute requests in Rust and return the result to Android asynchronously with callback. I'm testing code to execute the request in the command line and it works fine.
That is how it works on command line:
Callback struck:
struct Processor {
pub(crate) callback: Box<dyn FnMut(String)>,
}
impl Processor {
fn set_callback(&mut self, c: impl FnMut(String) + 'static) {
self.callback = Box::new(c);
}
fn process_events(&mut self, result: String) {
(self.callback)(result);
}
}
Tokio/reqwest:
const DATA_URL: &str = "https://pokeapi.co/api/v2/pokemon/1/";
#[tokio::main]
pub async fn load_swapi_async_with_cb(callback: Box<dyn FnMut(String)>) -> Result<(), Box<dyn std::error::Error>> {
println!("load_swload_swapi_async_with_cbapi_async started");
let mut cb = Processor {
callback: Box::new(callback),
};
let body = reqwest::get(DATA_URL)
.await?
.json::<HashMap<String, String>>()
.await?;
//println!("{:#?}", body);
let name = match body.get("name") {
Some(name) => name,
None => "Failed to parse"
}.to_string();
println!("Name is: {} ", name);
cb.process_events(name);
Ok(())
}
And JNI part:
#[no_mangle]
#[allow(non_snake_case)]
pub extern "C" fn Java_com_omg_app_greetings_MainActivity_callback(env: JNIEnv,
_class: JClass,
callback: JObject) {
static callback: dyn FnMut(String) + 'static = |name| {
let response = env.new_string(&name).expect("Couldn't create java string!");
env.call_method(callback, "rustCallbackResult", "(Ljava/lang/String;)V",
&[JValue::from(JObject::from(response))]).unwrap();
};
pokemon_api(callback);
}
And pokemon API method:
#[no_mangle]
pub extern fn pokemon_api(callback: impl FnMut(String) + 'static) {
let cb_box = Box::new(callback);
swapi::load_swapi_async_with_cb(cb_box);
}
The error I'm facing:
JNI ENV env non-constant value:
let response = env.new_string(&name).expect("Couldn't create java string!");
| ^^^ non-constant value
callback - doesn't have a size known at compile-time:
static callback: dyn FnMut(String) + 'static = |name| {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
I was checking how this working, but example seems to be out of date:
* https://github.com/mozilla/rust-android-gradle/blob/master/samples/rust/src/lib.rs
I fixed with my problem with the usage of the https://github.com/Dushistov/rust_swig
After you integrate it, it will autogenerate code and you can check how it does it.
Related
Let's say I have a native method which address is expfunction.address. I tried to intercept the function call and get its arguments, general idea:
Interceptor.attach(expfunction.address, {
onEnter(args) {
console.log("\n-----\n[*] Function is called!);
for(let i in args) {
... read value of args[i] ...
}
},
onLeave(retval) {
console.log("\t\n-----\n[*] Function returns");
console.log("\t[+] Returns:", retval);
}
})
The function's arguments can be any datatype such as jboolean, jstring, jobject, etc. So how to get exactly the datatype of args[i] and properly read the value of it.
By the way, Is for(let i in args) { ... } a correct way to enumerate args array? Since trying to get args.length raises RangeError: invalid array index.
What I have tried so far (and they all return blank):
Failed 1
onEnter(args) {
for(let i in args) {
var tmp = new NativePointer(args[i]);
console.log(tmp.readPointer());
}
}
Failed 2
function getType(value) {
return Object.prototype.toString.call(value).toLowerCase();
}
...
onEnter(args) {
for(let i in args) {
var tmp = new NativePointer(args[i]);
console.log("Type:", getType(tmp));
}
}
Thank you for reading to this point :)
Trying my first Flutter plugin, I try to invoke a method in both, the iOS and the Android-world. I successfully was able to invoke such a method without any parameters.
But now I would like to invoke a method that has parameters.
For iOS, I can't get it to work for some reason. (maybe it is just an autocomplete thing that I keep overseeing since VSCode is not autocompleting my Swift code). But maybe it is something else. Please any help on this.
Here is my code:
My lib (Flutter-world) looks like this:
import 'dart:async';
import 'package:flutter/services.dart';
class SomeName {
static const MethodChannel _channel =
const MethodChannel('myTestMethod');
static Future<String> get sendParamsTest async {
final String version = await _channel.invokeMethod('sendParams',<String, dynamic>{
'someInfo1': "test123",
'someInfo2': "hello",
});
return version;
}
}
.
My swift plugin (iOS-world) looks like this:
import Flutter
import UIKit
public class SwiftSomeNamePlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "myTestMethod", binaryMessenger: registrar.messenger())
let instance = SwiftSomeNamePlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: #escaping FlutterResult) {
// flutter cmds dispatched on iOS device :
if call.method == "sendParams" {
guard let args = call.arguments else {
result("iOS could not recognize flutter arguments in method: (sendParams)")
}
String someInfo1 = args["someInfo1"]
String someInfo2 = args["someInfo2"]
print(someInfo1)
print(someInfo2)
result("Params received on iOS = \(someInfo1), \(someInfo2)")
} else {
result("Flutter method not implemented on iOS")
}
}
}
The error messages say:
note: add arguments after the type to construct a value of the type
String someInfo1 = args["someInfo1"]
note: add arguments after the type to construct a value of the type
String someInfo2 = args["someInfo2"]
note: use '.self' to reference the type object
String someInfo1 = args["someInfo1"]
note: use '.self' to reference the type object
String someInfo2 = args["someInfo2"]
warning: expression of type 'String.Type' is unused
String someInfo1 = args["someInfo1"]
warning: expression of type 'String.Type' is unused
String someInfo2 = args["someInfo2"]
With the help of miguelpruivo, I found the solution.
Here is the working code:
The Flutter-world in Dart was correct:
import 'dart:async';
import 'package:flutter/services.dart';
class SomeName {
static const MethodChannel _channel =
const MethodChannel('myTestMethod');
static Future<String> get sendParamsTest async {
final String version = await _channel.invokeMethod('sendParams',<String, dynamic>{
'someInfo1': "test123",
'someInfo2': 3.22,
});
return version;
}
}
.
And here below, the iOS-world in Swift - now working as well...
(Dart's dynamic corresponds to Swift's Any)
(the method parameter is a dictionary of type [String:Any] - kind of like Swift's often used userInfo - therefore you need to cast at the receiver handler)...
import Flutter
import UIKit
public class SwiftSomeNamePlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "myTestMethod", binaryMessenger: registrar.messenger())
let instance = SwiftSomeNamePlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: #escaping FlutterResult) {
// flutter cmds dispatched on iOS device :
if call.method == "sendParams" {
guard let args = call.arguments else {
return
}
if let myArgs = args as? [String: Any],
let someInfo1 = myArgs["someInfo1"] as? String,
let someInfo2 = myArgs["someInfo2"] as? Double {
result("Params received on iOS = \(someInfo1), \(someInfo2)")
} else {
result(FlutterError(code: "-1", message: "iOS could not extract " +
"flutter arguments in method: (sendParams)", details: nil))
}
} else if call.method == "getPlatformVersion" {
result("Running on: iOS " + UIDevice.current.systemVersion)
} else {
result(FlutterMethodNotImplemented)
}
}
}
This looks like a swift syntax error.
You want to do let someInfo1 : String = args[“someInfo1”]
I'm building a C++ library with Visual Studio and using it in an Android Studio project that I run in an emulator. If I don't use the problem code below, the rest of the project works fine.
In my java class:
public native int androidmain();
public void aFunction() {
androidmain();
}
And in my C++ library:
#if defined( PLATFORM_ANDROID )
extern "C" {
JNIEXPORT jint JNICALL Java_com_TestsRunnerAndroid2015_TestsRunnerAndroid2015_androidmain( JNIEnv * env, jobject, jobjectArray argsObj ) {
OutputString( "androidmain called" ); // ALMODEBUG
jsize stringCount = 0;
if ( env == nullptr ) {
OutputString( "env is null" );
} else {
OutputString( "env is not null" );
}
if ( argsObj != nullptr ) {
OutputString( "argsObj was not null" );
stringCount = env->GetArrayLength( argsObj ); // CRASH HERE
OutputString( "Got argsObj length." );
} else {
OutputString( "argsObj was null" );
}
...
The output I get from this is:
D/TAG: androidmain called
D/TAG: env is not null
D/TAG: argsObj was not null
W/art: Suspending all threads took: 94.170ms
A/art: art/runtime/java_vm_ext.cc:470] JNI DETECTED ERROR IN APPLICATION: use of invalid jobject 0x12c62000
A/art: art/runtime/java_vm_ext.cc:470] from int com.TestsRunnerAndroid2015.TestsRunnerAndroid2015.androidmain()
Additional comment: I'm aware I'm sending no arguments, yet parsing an argument array. I'm expecting the count of objects to be zero.
In Java code you declare androidmain() as not taking any arguments, but in C code you declare Java_com_TestsRunnerAndroid2015_TestsRunnerAndroid2015_androidmain(...) as taking an array of objects as an argument.
In Java code, when calling androidmain() you are not supplying argsObj argument for Java_com_TestsRunnerAndroid2015_TestsRunnerAndroid2015_androidmain(...)
Try declaring androidmain as public native int androidmain(Object[] argsObj); and when you call it -- pass that argument.
I am studying on how to develop android sdk with gomobile, here is my use case:
The sdk will handle the file download and it will send its realtime received content to andoird, how could this be done?
I tried something like this, return a ReadCloser, then android will read from this stream :
func DownloadFile(url string) (io.ReadCloser, error) {
// download the file
resp, err := http.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
err = errors.New(fmt.Sprintf("requesrting url [%s] error [%s]!", url, resp.Status))
return nil, err
}
return resp.Body, nil
}
however from the compiled java class, there isn't even this method, why?
And I tried to return a channel, but it is the same result, not event compiled in target java class.
Is there any better way to do that? does gomobile supports callback (go sdk call this callback registered by android)? I can hardly find any documentation on that callback usage.
After some googling, I found some useful information, here is a good presentation.
https://talks.madriguera.me/2016/gomobile.slide#17
Finnally, I made my code work:
// define call back interface
type Downloader interface {
Forward(content []byte)
}
// callback instance
var downloader Downloader
// save the callback instance
func RegisterCallback(c Downloader) {
downloader = c
}
func Download(url string) error {
// download the file
resp, err := http.Get(url)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
err = errors.New(fmt.Sprintf("requesrting url [%s] error [%s]!", url, resp.Status))
return err
}
defer resp.Body.Close()
buf := make([]byte, 256)
copyBuffer(resp.Body, buf)
return nil
}
func copyBuffer(src io.Reader, buf []byte) (err error) {
for {
nr, er := src.Read(buf)
if nr > 0 {
content := buf[0:nr]
// callback
downloader.Forward(content)
}
if er == io.EOF {
break
}
if er != nil {
err = er
break
}
}
return nil
}
In Android, I just implement forward interface, then it works well:
// remember call registerCallback() first
#Override
public void forward(byte[] bytes) {
System.out.println("--------------");
System.out.println(new String(bytes));
}
I've some issue with executing Cordova custom plugin and passing arguments to it.
I'm using function cordova.exec(callback, errCallback, pluginName, pluginAction, pluginArgs), like this:
module BlankCordovaApp2 {
"use strict";
export module Application {
export function initialize() {
document.addEventListener('deviceready', onDeviceReady, false);
}
function onDeviceReady() {
document.addEventListener('pause', onPause, false);
document.addEventListener('resume', onResume, false);
var r = Math.floor((Math.random() * 1000) + 1);
log({ id: r, value: "test" + r },
function () { alert('pass') },
function () { alert('fail') });
}
function log (log, callback, errCallback) {
cordova.exec(callback,
errCallback,
"LogstashLogger",
"LOGGER_SERVICE_ACTION_LOG",
log
);
};
function onPause() {
// TODO: This application has been suspended. Save application state here.
}
function onResume() {
// TODO: This application has been reactivated. Restore application state here.
}
}
window.onload = function () {
Application.initialize();
}
}
This way, cordova is calling my plugin's:
public boolean execute(String action, String rawArgs, CallbackContext callbackContext)
which is fine.
However, I'd like also to pass string and jsonarray to my plugin. Unfortunatelly, whatever I pass to cordova.exec, always execute for (..., String rawArgs, ...) gets called, so:
cordova.exec(..., "test") calls execute with "test" as a rawArgs but WITH double quotes! What on earth...
cordova.exec(..., [{ id: 1, value: "test1" }, { id: 1, value: "test1" }] calls execute with [{"id":891,"value":"test891"},{"id":891,"value":"test891"}] as a String...
It look like calling my function log(...) is doing some strange type casting. But I can call to cordova.exec directly only with string[] as a type, but then when I call cordova.exec(..., [ "test" ]) it should call execute with jsonarray as parameter with one string element, but it's acctualy calling execute with string parameter with value ["test"]
So question is, how to call cordova.exec properly?
Try something along these lines:
Javascript
var json1 = {foo: "bar"};
var json2 = {bar: "foo"};
cordova.exec(
successCallback,
errorCallback,
'MyPlugin',
'myAction',
[json1,json2]
);
Java
public class MyPlugin extends CordovaPlugin {
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if ("myAction".equals(action)){
JSONObject json1 = args.getJSONObject(0);
JSONObject json2 = args.getJSONObject(1);
callbackContext.success();
}
return true;
}
}
UPDATE
Here's a working example project which illustrates how JSON can be passed from Javascript to Java on the Android platform: http://ge.tt/api/1/files/2CJwqVL2/0/blob?download