Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement callbacks that return a value in Swift/Kotlin 🥳 #417

Merged
merged 6 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 21 additions & 27 deletions example/src/getTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -828,33 +828,27 @@ export function getTests(
.didNotThrow()
.equals(433)
),
...('getValueFromJsCallback' in testObject
? [
createTest('getValueFromJsCallback(...)', async () =>
(
await it(async () => {
let value: string | undefined
await testObject.getValueFromJsCallback(
() => 'hello',
(val) => {
value = val
}
)
return value
})
)
.didNotThrow()
.equals('hello')
),
createTest('getValueFromJSCallbackAndWait(...)', async () =>
(await it(() => testObject.getValueFromJSCallbackAndWait(() => 73)))
.didNotThrow()
.equals(73)
),
]
: [
// Swift/Kotlin Test Object does not support JS callbacks _that return a value_ yet!
]),
createTest('getValueFromJsCallback(...)', async () =>
(
await it(async () => {
let value: string | undefined
await testObject.getValueFromJsCallback(
() => 'hello',
(val) => {
value = val
}
)
return value
})
)
.didNotThrow()
.equals('hello')
),
createTest('getValueFromJSCallbackAndWait(...)', async () =>
(await it(() => testObject.getValueFromJSCallbackAndWait(() => 73)))
.didNotThrow()
.equals(73)
),
createTest('callAll(...)', async () =>
(
await it(async () => {
Expand Down
10 changes: 6 additions & 4 deletions packages/nitrogen/src/syntax/kotlin/KotlinCxxBridgedType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -538,30 +538,32 @@ export class KotlinCxxBridgedType implements BridgedType<'kotlin', 'c++'> {
// void: resolve()
return `
[&]() {
jni::local_ref<JPromise::javaobject> __promise = JPromise::create();
jni::local_ref<JPromise::javaobject> __localPromise = JPromise::create();
jni::global_ref<JPromise::javaobject> __promise = jni::make_global(__localPromise);
${parameterName}->addOnResolvedListener([=]() {
__promise->cthis()->resolve(JUnit::instance());
});
${parameterName}->addOnRejectedListener([=](const std::exception_ptr& __error) {
auto __jniError = jni::getJavaExceptionForCppException(__error);
__promise->cthis()->reject(__jniError);
});
return __promise;
return __localPromise;
}()
`.trim()
} else {
// T: resolve(T)
return `
[&]() {
jni::local_ref<JPromise::javaobject> __promise = JPromise::create();
jni::local_ref<JPromise::javaobject> __localPromise = JPromise::create();
jni::global_ref<JPromise::javaobject> __promise = jni::make_global(__localPromise);
${parameterName}->addOnResolvedListener([=](const ${resolvingType}& __result) {
__promise->cthis()->resolve(${indent(bridge.parseFromCppToKotlin('__result', 'c++', true), ' ')});
});
${parameterName}->addOnRejectedListener([=](const std::exception_ptr& __error) {
auto __jniError = jni::getJavaExceptionForCppException(__error);
__promise->cthis()->reject(__jniError);
});
return __promise;
return __localPromise;
}()
`.trim()
}
Expand Down
18 changes: 15 additions & 3 deletions packages/nitrogen/src/syntax/kotlin/KotlinFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ class ${name} {
}
`.trim()

const cppReturnType = functionType.returnType.getCode('c++')
const bridgedReturn = new KotlinCxxBridgedType(functionType.returnType)

const cppParams = functionType.parameters.map((p) => {
const bridge = new KotlinCxxBridgedType(p)
const type = bridge.asJniReferenceType('alias')
Expand All @@ -73,6 +74,17 @@ class ${name} {
const jniClassDescriptor = NitroConfig.getAndroidPackage('c++/jni', name)
const cxxNamespace = NitroConfig.getCxxNamespace('c++')
const typename = functionType.getCode('c++')
let callBody: string
if (functionType.returnType.kind === 'void') {
// It returns void
callBody = `_func(${indent(paramsForward.join(', '), ' ')});`
} else {
// It returns a type!
callBody = `
${functionType.returnType.getCode('c++')} __result = _func(${indent(paramsForward.join(', '), ' ')});
return ${bridgedReturn.parseFromCppToKotlin('__result', 'c++')};
`.trim()
}

const bridged = new KotlinCxxBridgedType(functionType)
const imports = bridged
Expand Down Expand Up @@ -105,8 +117,8 @@ namespace ${cxxNamespace} {
}

public:
${cppReturnType} call(${cppParams.join(', ')}) {
return _func(${indent(paramsForward.join(', '), ' ')});
${bridgedReturn.asJniReferenceType('local')} call(${cppParams.join(', ')}) {
${indent(callBody, ' ')}
}

public:
Expand Down
9 changes: 3 additions & 6 deletions packages/nitrogen/src/syntax/swift/SwiftCxxBridgedType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,6 @@ export class SwiftCxxBridgedType implements BridgedType<'swift', 'c++'> {
const promise = getTypeAs(this.type, PromiseType)
switch (language) {
case 'swift': {
const resolvingTypeBridge = new SwiftCxxBridgedType(
promise.resultingType
)
if (promise.resultingType.kind === 'void') {
// It's void - resolve()
const rejecterFunc = new FunctionType(new VoidType(), [
Expand Down Expand Up @@ -380,8 +377,8 @@ export class SwiftCxxBridgedType implements BridgedType<'swift', 'c++'> {
return `
{ () -> ${promise.getCode('swift')} in
let __promise = ${promise.getCode('swift')}()
let __resolver = { (__result: ${resolvingTypeBridge.getTypeCode('swift')}) in
__promise.resolve(withResult: ${indent(resolvingTypeBridge.parseFromCppToSwift('__result', 'swift'), ' ')})
let __resolver = { (__result: ${promise.resultingType.getCode('swift')}) in
__promise.resolve(withResult: __result)
}
let __rejecter = { (__error: Error) in
__promise.reject(withError: __error)
Expand Down Expand Up @@ -543,7 +540,7 @@ case ${i}:
let __sharedClosure = bridge.share_${bridge.specializationName}(${cppParameterName})
return { ${signature} in
let __result = __sharedClosure.pointee.call(${paramsForward.join(', ')})
return ${indent(resultBridged.parseFromSwiftToCpp('__result', 'swift'), ' ')}
return ${indent(resultBridged.parseFromCppToSwift('__result', 'swift'), ' ')}
}
}()`.trim()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,20 @@ class HybridTestObjectKotlin: HybridTestObjectSwiftKotlinSpec() {
callback(value)
}

override fun getValueFromJSCallbackAndWait(getValue: (() -> Promise<Double>)): Promise<Double> {
return Promise.async {
val jsResult = getValue().await()
return@async jsResult
}
}

override fun getValueFromJsCallback(callback: (() -> Promise<String>), andThenCall: ((valueFromJs: String) -> Unit)): Promise<Unit> {
return Promise.async {
val jsResult = callback().await()
andThenCall(jsResult)
}
}

override fun callAll(first: () -> Unit, second: () -> Unit, third: () -> Unit) {
first()
second()
Expand Down
15 changes: 15 additions & 0 deletions packages/react-native-nitro-image/ios/HybridTestObjectSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ class HybridTestObjectSwift : HybridTestObjectSwiftKotlinSpec {
func callWithOptional(value: Double?, callback: @escaping ((_ maybe: Double?) -> Void)) throws -> Void {
callback(value)
}

func getValueFromJSCallbackAndWait(getValue: @escaping (() -> Promise<Double>)) throws -> Promise<Double> {
return .async {
let jsResult = try await getValue().await()
return jsResult
}
}

func getValueFromJsCallback(callback: @escaping (() -> Promise<String>), andThenCall: @escaping ((_ valueFromJs: String) -> Void)) throws -> Promise<Void> {
return .async {
let jsResult = try await callback().await()
andThenCall(jsResult)
}
}


func bounceStrings(array: [String]) throws -> [String] {
return array
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include "JFunc_void_std__vector_Powertrain_.hpp"
#include "JFunc_void.hpp"
#include "JFunc_void_std__optional_double_.hpp"
#include "JFunc_std__shared_ptr_Promise_double__.hpp"
#include "JFunc_std__shared_ptr_Promise_std__string__.hpp"
#include "JHybridBaseSpec.hpp"
#include "JHybridChildSpec.hpp"
#include <NitroModules/JNISharedPtr.hpp>
Expand All @@ -43,6 +45,9 @@ int initialize(JavaVM* vm) {
margelo::nitro::image::JFunc_void::registerNatives();
margelo::nitro::image::JFunc_void::registerNatives();
margelo::nitro::image::JFunc_void_std__optional_double_::registerNatives();
margelo::nitro::image::JFunc_std__shared_ptr_Promise_double__::registerNatives();
margelo::nitro::image::JFunc_std__shared_ptr_Promise_std__string__::registerNatives();
margelo::nitro::image::JFunc_void_std__string::registerNatives();
margelo::nitro::image::JHybridBaseSpec::registerNatives();
margelo::nitro::image::JHybridChildSpec::registerNatives();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
///
/// JFunc_std__shared_ptr_Promise_double__.hpp
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
/// https://github.com/mrousavy/nitro
/// Copyright © 2024 Marc Rousavy @ Margelo
///

#pragma once

#include <fbjni/fbjni.h>
#include <functional>

#include <functional>
#include <NitroModules/Promise.hpp>
#include <NitroModules/JPromise.hpp>

namespace margelo::nitro::image {

using namespace facebook;

/**
* C++ representation of the callback Func_std__shared_ptr_Promise_double__.
* This is a Kotlin `() -> Promise<Double>`, backed by a `std::function<...>`.
*/
struct JFunc_std__shared_ptr_Promise_double__ final: public jni::HybridClass<JFunc_std__shared_ptr_Promise_double__> {
public:
static jni::local_ref<JFunc_std__shared_ptr_Promise_double__::javaobject> fromCpp(const std::function<std::shared_ptr<Promise<double>>()>& func) {
return JFunc_std__shared_ptr_Promise_double__::newObjectCxxArgs(func);
}

public:
jni::local_ref<JPromise::javaobject> call() {
std::shared_ptr<Promise<double>> __result = _func();
return [&]() {
jni::local_ref<JPromise::javaobject> __localPromise = JPromise::create();
jni::global_ref<JPromise::javaobject> __promise = jni::make_global(__localPromise);
__result->addOnResolvedListener([=](const double& __result) {
__promise->cthis()->resolve(jni::JDouble::valueOf(__result));
});
__result->addOnRejectedListener([=](const std::exception_ptr& __error) {
auto __jniError = jni::getJavaExceptionForCppException(__error);
__promise->cthis()->reject(__jniError);
});
return __localPromise;
}();
}

public:
static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/image/Func_std__shared_ptr_Promise_double__;";
static void registerNatives() {
registerHybrid({makeNativeMethod("call", JFunc_std__shared_ptr_Promise_double__::call)});
}

private:
explicit JFunc_std__shared_ptr_Promise_double__(const std::function<std::shared_ptr<Promise<double>>()>& func): _func(func) { }

private:
friend HybridBase;
std::function<std::shared_ptr<Promise<double>>()> _func;
};

} // namespace margelo::nitro::image
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
///
/// JFunc_std__shared_ptr_Promise_std__string__.hpp
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
/// https://github.com/mrousavy/nitro
/// Copyright © 2024 Marc Rousavy @ Margelo
///

#pragma once

#include <fbjni/fbjni.h>
#include <functional>

#include <functional>
#include <NitroModules/Promise.hpp>
#include <string>
#include <NitroModules/JPromise.hpp>

namespace margelo::nitro::image {

using namespace facebook;

/**
* C++ representation of the callback Func_std__shared_ptr_Promise_std__string__.
* This is a Kotlin `() -> Promise<String>`, backed by a `std::function<...>`.
*/
struct JFunc_std__shared_ptr_Promise_std__string__ final: public jni::HybridClass<JFunc_std__shared_ptr_Promise_std__string__> {
public:
static jni::local_ref<JFunc_std__shared_ptr_Promise_std__string__::javaobject> fromCpp(const std::function<std::shared_ptr<Promise<std::string>>()>& func) {
return JFunc_std__shared_ptr_Promise_std__string__::newObjectCxxArgs(func);
}

public:
jni::local_ref<JPromise::javaobject> call() {
std::shared_ptr<Promise<std::string>> __result = _func();
return [&]() {
jni::local_ref<JPromise::javaobject> __localPromise = JPromise::create();
jni::global_ref<JPromise::javaobject> __promise = jni::make_global(__localPromise);
__result->addOnResolvedListener([=](const std::string& __result) {
__promise->cthis()->resolve(jni::make_jstring(__result));
});
__result->addOnRejectedListener([=](const std::exception_ptr& __error) {
auto __jniError = jni::getJavaExceptionForCppException(__error);
__promise->cthis()->reject(__jniError);
});
return __localPromise;
}();
}

public:
static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/image/Func_std__shared_ptr_Promise_std__string__;";
static void registerNatives() {
registerHybrid({makeNativeMethod("call", JFunc_std__shared_ptr_Promise_std__string__::call)});
}

private:
explicit JFunc_std__shared_ptr_Promise_std__string__(const std::function<std::shared_ptr<Promise<std::string>>()>& func): _func(func) { }

private:
friend HybridBase;
std::function<std::shared_ptr<Promise<std::string>>()> _func;
};

} // namespace margelo::nitro::image
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ namespace margelo::nitro::image {

public:
void call() {
return _func();
_func();
}

public:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ namespace margelo::nitro::image {

public:
void call(jni::alias_ref<jni::JDouble> maybe) {
return _func(maybe != nullptr ? std::make_optional(maybe->value()) : std::nullopt);
_func(maybe != nullptr ? std::make_optional(maybe->value()) : std::nullopt);
}

public:
Expand Down
Loading
Loading