Hello,
I figured I’m not alone facing this, even though it should be fixed with the next Expo SDK release which is due sometime by next month.
I did install Datadog’s RN RUM Collection following their doc:
It did work right away with iOS, but it crashed the build for Android:
Class 'kotlin.Unit' was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.5.1, expected version is 1.1.15.
[stderr] The class is loaded from /home/expo/.gradle/caches/transforms-3/ed0646599df2d1ac2019df6e9af2e8ac/transformed/jetified-kotlin-stdlib-1.5.31.jar!/kotlin/Unit.class
w: Detected multiple Kotlin daemon sessions at build/kotlin/sessions
[stderr] FAILURE: Build failed with an exception.
I figured that DD’s team wrote their plugin for the latest everything (RN/Kotlin/…), whereas Expo is a bit behind (as it’s a full ecosystem to keep in balance, it’s near impossible for them to keep it all bleeding edge.
First I followed an advice to use android.(production|staging|development).image = "latest"
in eas.json
, to make sure I’m using Java 11.
So looking it up on the interwebs, I came up with the following solution, which is to force kotlin’s version:
buildscript {
ext {
kotlinVersion = "1.5.10"
kotlin_version = "1.5.10"
}
}
in the project’s build.gradle
(I put both because I’m not sure which one works, and I’ve seen both in the answers… ). Hopefully, thanks to expo’s amazing work, you don’t need to eject to make it work, you can stay in managed mode and hack your way through using a plugin, so I wrote the following plugin:
import {
ConfigPlugin,
withProjectBuildGradle,
withPlugins,
createRunOncePlugin,
WarningAggregator,
} from "@expo/config-plugins";
const setKotlinVersion = (buildGradle: string) => {
// buildscript
// https://stackoverflow.com/questions/67699823/module-was-compiled-with-an-incompatible-version-of-kotlin-the-binary-version-o
return `${buildGradle}
buildscript {
ext {
kotlinVersion = "1.5.10"
kotlin_version = "1.5.10"
}
}
`;
};
const withAndroidBuildGradleMods: ConfigPlugin = config => {
return withProjectBuildGradle(config, config => {
if (config.modResults.language === "groovy") {
config.modResults.contents = setKotlinVersion(config.modResults.contents);
} else {
WarningAggregator.addWarningAndroid(
"with-android-build-gradle",
`Cannot automatically configure project build.gradle if it's not groovy`,
);
}
return config;
});
};
const withKotlinFix: ConfigPlugin = config => {
return withPlugins(config, [
withAndroidBuildGradleMods,
]);
};
export default createRunOncePlugin(withKotlinFix, "withKotlinFix", "0.0.1");
which I placed within $PROJECT_PATH/plugin/src/withKotlinFix.ts
.
I was happy and thought the issue was fixed.
Until it crashed again with:
[stderr] > A failure occurred while executing com.android.build.gradle.internal.tasks.CheckAarMetadataWorkAction
[stderr] > The minCompileSdk (31) specified in a
[stderr] dependency's AAR metadata (META-INF/com/android/build/gradle/aar-metadata.properties)
[stderr] is greater than this module's compileSdkVersion (android-30).
[stderr] Dependency: androidx.work:work-runtime:2.7.0.
[stderr] AAR metadata file: /home/expo/.gradle/caches/transforms-3/ffee51ff6db99f884b0e8a6a5d65a88b/transformed/work-runtime-2.7.0/META-INF/com/android/build/gradle/aar-metadata.properties.
Which I solved with another plugin, fixing the SDK version, plugins/src/withCustomAndroidVersion.ts
:
import {
withProjectBuildGradle,
withPlugins,
createRunOncePlugin,
ConfigPlugin,
} from "@expo/config-plugins";
function setMinSdkVersion(buildGradle: string, targetVersion: number) {
const regexpCurrentVersion = /\bminSdkVersion\s*=\s*(\d+)/;
const match = buildGradle.match(regexpCurrentVersion);
if (match) {
const version = parseInt(match[1], 10);
if (version < targetVersion) {
buildGradle = buildGradle.replace(
/\bminSdkVersion\s*=\s*\d+/,
`minSdkVersion = ${targetVersion}`
);
} else {
console.warn(`WARN: minSdkVersion is already >= ${version}`);
}
}
return buildGradle;
}
function setCompileSdkVersion(buildGradle: string, targetVersion: number) {
const regexpCurrentVersion = /\bcompileSdkVersion\s*=\s*(\d+)/;
const match = buildGradle.match(regexpCurrentVersion);
if (match) {
const version = parseInt(match[1], 10);
if (version < targetVersion) {
buildGradle = buildGradle.replace(
/\bcompileSdkVersion\s*=\s*\d+/,
`compileSdkVersion = ${targetVersion}`
);
} else {
console.warn(`WARN: compileSdkVersion is already >= ${version}`);
}
}
return buildGradle;
}
function setTargetSdkVersion(buildGradle: string, targetVersion: number) {
const regexpCurrentVersion = /\btargetSdkVersion\s*=\s*(\d+)/;
const match = buildGradle.match(regexpCurrentVersion);
if (match) {
const version = parseInt(match[1], 10);
if (version < targetVersion) {
buildGradle = buildGradle.replace(
/\btargetSdkVersion\s*=\s*\d+/,
`targetSdkVersion = ${targetVersion}`
);
} else {
console.warn(`WARN: targetSdkVersion is already >= ${version}`);
}
}
return buildGradle;
}
const withAndroidSdkVersions: ConfigPlugin<{
minSdkVersion?: number;
compileSdkVersion?: number;
targetSdkVersion?: number;
}> = (config, {
minSdkVersion,
compileSdkVersion,
targetSdkVersion
}) => {
return withProjectBuildGradle(config, (config) => {
if (config.modResults.language !== 'groovy')
throw new Error("Can't use withAndroidSdkVersions EAS Plugin as build.gradle is not groovy");
if (minSdkVersion)
config.modResults.contents = setMinSdkVersion(config.modResults.contents, minSdkVersion);
if (compileSdkVersion)
config.modResults.contents = setCompileSdkVersion(config.modResults.contents, compileSdkVersion);
if (targetSdkVersion)
config.modResults.contents = setTargetSdkVersion(config.modResults.contents, targetSdkVersion);
return config;
});
};
const withCustomAndroidVersion: ConfigPlugin = (config, props) => {
return withPlugins(config, [
[withAndroidSdkVersions, props],
]);
}
export default createRunOncePlugin(withCustomAndroidVersion, "withCustomAndroidVersion", "0.0.1");
N.B.: here’s an alternative way to do the above
But that was not enough, so I also forced the WorkManager version:
import {
ConfigPlugin,
withAppBuildGradle,
withPlugins,
createRunOncePlugin,
WarningAggregator,
} from "@expo/config-plugins";
const setWorkManagerVersion = (buildGradle: string) => {
return `${buildGradle}
dependencies {
def work_version = "2.7.1"
// Force WorkManager 2.6.0 for transitive dependency
implementation("androidx.work:work-runtime-ktx:$work_version") {
force = true
}
implementation("androidx.work:work-runtime:$work_version") {
force = true
}
}
`;
}
const withAndroidBuildGradleMods: ConfigPlugin = config => {
return withAppBuildGradle(config, config => {
if (config.modResults.language === "groovy") {
config.modResults.contents = setWorkManagerVersion(config.modResults.contents);
} else {
WarningAggregator.addWarningAndroid(
"with-android-build-gradle",
`Cannot automatically configure project build.gradle if it's not groovy`,
);
}
return config;
});
};
const withWorkManagerVersionFix: ConfigPlugin = config => {
return withPlugins(config, [
withAndroidBuildGradleMods,
]);
};
export default createRunOncePlugin(withWorkManagerVersionFix, "withWorkManagerVersionFix", "0.0.1");
Hopefully that’d be all.
And it was, I finally had the green light on the Android build… but a huge fail when submitting to Google Play. It rejected me saying:
Google Api Error: Invalid request - You uploaded an APK or Android App Bundle which has an activity, activity alias, service or broadcast receiver with intent filter, but without 'android:exported' property set
So I did two patches, one is to add android:exported
in my app’s AndroidManifest.xml
:
import {
ConfigPlugin,
withAndroidManifest,
withPlugins,
createRunOncePlugin,
WarningAggregator,
} from "@expo/config-plugins";
const withAndroidManifestFix: ConfigPlugin = config => {
return withAndroidManifest(config, config => {
if (config.modResults.manifest.application?.[0]?.activity?.[0]?.$) {
config.modResults.manifest.application[0].activity[0].$["android:exported"] = "true";
} else {
WarningAggregator.addWarningAndroid(
"with-android-manifest",
`Cannot automatically configure AndroidManifest if there's no activity`,
);
}
return config;
});
};
const withAndroidManifestFix: ConfigPlugin = config => {
return withPlugins(config, [
withAndroidManifestFix,
]);
};
export default createRunOncePlugin(withAndroidManifestFix, "withAndroidManifestFix", "0.0.1");
And the other was to use patch-package, edit the expo-dev-launcher
’s AndroidManifest.xml
file, and insert android:exported="true"
within the <activity />
tag, i.e.:
% yarn add patch-package postinstall-postinstall
% vim node_modules/expo-dev-launcher/android/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="expo.modules.devlauncher">
<application>
<activity
android:name="expo.modules.devlauncher.launcher.DevLauncherActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.DevLauncher.LauncherActivity"
android:launchMode="singleTask"
android:exported="true"
>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
% yarn run patch-package expo-dev-launcher
% git add yarn.lock package.json patches
% git commit -m "Adds patch on expo-dev-launcher"
Then I did try again, and voilà the build and submit worked!
And to orchestrate all my plugins, here’s the plugins
field of my app.config.js
:
plugins: [
/*
* The following four plugins are there to fix issues with RN DD RUM
* They should be solved (hopefully) with the next SDK release of Expo.
*/
"./plugin/build/withKotlinFix",
[
"./plugin/build/withCustomAndroidVersion",
{
// minSdkVersion: 24,
"compileSdkVersion": 31,
"targetSdkVersion": 31
}
],
"./plugin/build/withWorkManagerVersionFix",
"./plugin/build/withAndroidManifestFix",
],
The above changes are what I did to make it work, it might be possible some steps are not necessary, but because it takes a lot of time to do a full build cycle to test it through, and because I was in a hurry to publish my app, I did not take time to have a scientific approach and make sure what steps really are necessary. I leave that as an exercice to the reader .
N.B.: Because I always do TS whenever possible, to use the above plugins, you need to add the following tsconfig.json within ./plugins
and make sure tsc --build plugins
is ran in a yarn postinstall hook
:
{
"extends": "expo-module-scripts/tsconfig.plugin",
"compilerOptions": {
"outDir": "build",
"rootDir": "src",
"declaration": true
},
"include": ["./src"]
}