Skip to content
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
2 changes: 2 additions & 0 deletions packages/expo-application/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### 🐛 Bug fixes

- [Android] Fix `PromiseAlreadySettledException` crash in `getInstallReferrerAsync` when `onInstallReferrerServiceDisconnected` gets called after promise settlement. ([#43419](https://github.com/expo/expo/pull/43419) by [@nishan](https://github.com/intergalacticspacehighway))

### 💡 Others

## 55.0.8 — 2026-02-25
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,14 @@ class ApplicationModule : Module() {

AsyncFunction("getInstallReferrerAsync") { promise: Promise ->
val installReferrer = StringBuilder()
var isSettled = false

val referrerClient = InstallReferrerClient.newBuilder(context).build()

referrerClient.startConnection(object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
if (isSettled) return

when (responseCode) {
InstallReferrerClient.InstallReferrerResponse.OK -> {
// Connection established and response received
Expand All @@ -78,22 +81,33 @@ class ApplicationModule : Module() {
} catch (e: RemoteException) {
promise.reject("ERR_APPLICATION_INSTALL_REFERRER_REMOTE_EXCEPTION", "RemoteException getting install referrer information. This may happen if the process hosting the remote object is no longer available.", e)
return
} finally {
isSettled = true
}
promise.resolve(installReferrer.toString())
}

InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> // API not available in the current Play Store app
InstallReferrerClient.InstallReferrerResponse.FEATURE_NOT_SUPPORTED -> { // API not available in the current Play Store app
isSettled = true
promise.reject("ERR_APPLICATION_INSTALL_REFERRER_UNAVAILABLE", "The current Play Store app doesn't provide the installation referrer API, or the Play Store may not be installed.", null)
}

InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE -> // Connection could not be established
InstallReferrerClient.InstallReferrerResponse.SERVICE_UNAVAILABLE -> { // Connection could not be established
isSettled = true
promise.reject("ERR_APPLICATION_INSTALL_REFERRER", "General error retrieving the install referrer: response code $responseCode", null)
}

else -> promise.reject("ERR_APPLICATION_INSTALL_REFERRER", "General error retrieving the install referrer: response code $responseCode", null)
else -> {
isSettled = true
promise.reject("ERR_APPLICATION_INSTALL_REFERRER", "General error retrieving the install referrer: response code $responseCode", null)
}
}
referrerClient.endConnection()
}

override fun onInstallReferrerServiceDisconnected() {
if (isSettled) return
isSettled = true
promise.reject("ERR_APPLICATION_INSTALL_REFERRER_SERVICE_DISCONNECTED", "Connection to install referrer service was lost.", null)
}
})
Expand Down
3 changes: 1 addition & 2 deletions packages/expo-modules-core/android/cmake/main.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,9 @@ file(
"${main_dir}/decorators/*.cpp"
"${main_dir}/installers/*.cpp"
"${main_dir}/worklets/*.cpp"
"${main_dir}/fabric/*.cpp"
)

file(GLOB fabric_andorid_sources "${ANDROID_SRC_DIR}/fabric/*.cpp")

add_library(
expo-modules-core
SHARED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,19 @@ class ComposeViewProp(
anyType: AnyType,
val property: KProperty1<*, *>
) : AnyViewProp(name, anyType) {
private var _isStateProp = false

@Suppress("UNCHECKED_CAST")
override fun set(prop: Dynamic, onView: View, appContext: AppContext?) {
setPropDirectly(prop, onView, appContext)
}

override fun set(prop: Any?, onView: View, appContext: AppContext?) {
setPropDirectly(prop, onView, appContext)
}

@Suppress("UNCHECKED_CAST")
private fun setPropDirectly(prop: Any?, onView: View, appContext: AppContext?) {
exceptionDecorator({
PropSetException(name, onView::class, it)
}) {
Expand Down Expand Up @@ -50,5 +60,13 @@ class ComposeViewProp(
}
}

fun asStateProp(): ComposeViewProp {
_isStateProp = true
return this
}

override val isNullable: Boolean = anyType.kType.isMarkedNullable

override val isStateProp: Boolean
get() = _isStateProp
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
#include "worklets/WorkletNativeRuntime.h"

#if RN_FABRIC_ENABLED
#include "FabricComponentsRegistry.h"
#include "fabric/FabricComponentsRegistry.h"
#include "fabric/NativeStatePropsGetter.h"
#endif

#include <jni.h>
Expand Down Expand Up @@ -62,6 +63,7 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {

#if RN_FABRIC_ENABLED
expo::FabricComponentsRegistry::registerNatives();
expo::NativeStatePropsGetter::registerNatives();
#endif
});
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#include "AndroidExpoViewComponentDescriptor.h"

namespace expo {

// TODO(@lukmccall): Ask for public API to access RawProps internals
struct RawPropsAccessor {
react::RawPropsParser *parser_{nullptr};
react::RawProps::Mode mode_;
jsi::Runtime *runtime_{};
jsi::Value value_;
};

void AndroidExpoViewComponentDescriptor::setStateProps(
const std::unordered_map<
std::string,
std::shared_ptr<FrontendConverter>
> &stateProps
) {
stateProps_ = stateProps;
}

react::Props::Shared AndroidExpoViewComponentDescriptor::cloneProps(
const react::PropsParserContext &context,
const react::Props::Shared &props,
react::RawProps rawProps
) const {
if (!props && rawProps.isEmpty()) {
return ExpoViewShadowNode<AndroidExpoViewProps>::defaultSharedProps();
}

rawProps.parse(rawPropsParser_);

auto shadowNodeProps = std::make_shared<AndroidExpoViewProps>(
context,
props ? dynamic_cast<const AndroidExpoViewProps &>(*props)
: *ExpoViewShadowNode<AndroidExpoViewProps>::defaultSharedProps(),
rawProps,
filterObjectKeys_
);

// TODO(@lukmccall): We probably can remove this loop
if (react::ReactNativeFeatureFlags::enableCppPropsIteratorSetter()) {
#ifdef RN_SERIALIZABLE_STATE
const auto &dynamic = shadowNodeProps->rawProps;
#else
const auto &dynamic = static_cast<folly::dynamic>(rawProps);
#endif
for (const auto &pair: dynamic.items()) {
const auto &name = pair.first.getString();
shadowNodeProps->setProp(
context,
RAW_PROPS_KEY_HASH(name),
name.c_str(),
react::RawValue(pair.second)
);
}
}

if (!stateProps_.empty()) {
JNIEnv *env = jni::Environment::current();
const auto &rawPropsAccessor = *((RawPropsAccessor *) &rawProps);

jsi::Runtime &runtime = *rawPropsAccessor.runtime_;
const auto jsiProps = rawPropsAccessor.value_.asObject(runtime);

for (const auto &statePropPair: stateProps_) {
const auto &[propName, converter] = statePropPair;

const auto jsPropName = jsi::String::createFromUtf8(runtime, propName);
if (!jsiProps.hasProperty(runtime, jsPropName)) {
continue; // Property does not exist on the JS object
}

const auto value = jsiProps.getProperty(runtime, jsPropName);

if (shadowNodeProps->statePropsDiff == nullptr) {
shadowNodeProps->statePropsDiff = jni::make_global(
jni::JHashMap<jstring, jobject>::create()
);
}

jobject jConvertedValue = converter->convert(runtime, env, value);
shadowNodeProps->statePropsDiff->put(
jni::make_jstring(propName),
jConvertedValue
);
}
}

return shadowNodeProps;
}

void AndroidExpoViewComponentDescriptor::adopt(react::ShadowNode &shadowNode) const {
react_native_assert(dynamic_cast<ExpoShadowNode *>(&shadowNode));

Base::adopt(shadowNode);

const auto snode = dynamic_cast<ExpoShadowNode *>(&shadowNode);
auto &props = *std::static_pointer_cast<const AndroidExpoViewProps>(
snode->getProps());
snode->getStateData().statePropsDiff = props.statePropsDiff;
props.statePropsDiff = nullptr;
}

} // namespace expo
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#pragma once

#include "ExpoViewComponentDescriptor.h"
#include "AndroidExpoViewProps.h"
#include "AndroidExpoViewState.h"
#include "../types/FrontendConverter.h"

namespace react = facebook::react;

namespace expo {

class AndroidExpoViewComponentDescriptor
: public ExpoViewComponentDescriptor<ExpoViewShadowNode<AndroidExpoViewProps, AndroidExpoViewState>> {
public:
using Base = ExpoViewComponentDescriptor<ExpoViewShadowNode<AndroidExpoViewProps, AndroidExpoViewState>>;
using ExpoShadowNode = ExpoViewShadowNode<AndroidExpoViewProps, AndroidExpoViewState>;

using Base::ExpoViewComponentDescriptor;

void setStateProps(
const std::unordered_map<
std::string,
std::shared_ptr<FrontendConverter>
> &stateProps
);

react::Props::Shared cloneProps(
const react::PropsParserContext &context,
const react::Props::Shared &props,
react::RawProps rawProps
) const override;

void adopt(react::ShadowNode &shadowNode) const override;

private:
std::unordered_map<
std::string,
std::shared_ptr<FrontendConverter>
> stateProps_;

std::function<bool(const std::string &)> filterObjectKeys_ = [this](const std::string &key) {
return stateProps_.find(key) != stateProps_.end();
};
};

} // namespace expo
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include "AndroidExpoViewProps.h"

namespace expo {

AndroidExpoViewProps::AndroidExpoViewProps(
const facebook::react::PropsParserContext &context,
const AndroidExpoViewProps &sourceProps,
const facebook::react::RawProps &rawProps,
const std::function<bool(const std::string &)> &filterObjectKeys
) : ExpoViewProps(context, sourceProps, rawProps, filterObjectKeys),
statePropsDiff(nullptr) {
}

AndroidExpoViewProps::~AndroidExpoViewProps() {
if (statePropsDiff != nullptr) {
jni::ThreadScope::WithClassLoader([this] {
jni::Environment::current()->DeleteGlobalRef(statePropsDiff.release());
});
}
}

} // namespace expo
Loading
Loading