react-native-purchase-ui common-mark duplicate classes error (Dependency clash)
https://community.revenuecat.com/sdks-51/react-native-purchase-ui-common-mark-duplicate-classes-error-4586
we currently cant use crisp sdk if we have revenue cat in react native expo apps due to this issue.
is there a fix for expo apps?
Have you already tried this?
https://github.com/RevenueCat/purchases-android/issues/1667#issuecomment-2063155867
Yes in my own way, but this will not work sustainably. We shouldn't be editing the build.gradle directly in expo managed it apps. As it will get overwritten in prebuilds.
I had to create a hacky expo plugin which I'm not confident in but works for now.
Any longterm solutions?
@raghav-phonebox this is my hacky solution
/**
* This plugin configures the Crisp SDK integration for Android builds.
*
* Purpose:
* - Handles commonmark dependency conflicts in Crisp SDK
* - Resolves notification icon and color conflicts with Firebase
*
* What it does:
* 1. Excludes conflicting commonmark dependencies that cause duplicate class errors
* 2. Configures Android packaging options to handle resource conflicts
* 3. Adds the Crisp SDK dependency with proper exclusions
* 4. Adds manifest rules to handle notification conflicts
*
* Why we need it:
* The Crisp SDK has conflicting dependencies with other libraries in the project,
* specifically around the commonmark library and notification resources. This plugin ensures clean integration
* by managing these conflicts through Gradle configurations.
* links: https://community.revenuecat.com/sdks-51/react-native-purchase-ui-common-mark-duplicate-classes-error-4586
* links: https://github.com/RevenueCat/purchases-android/issues/1667
* links: https://github.com/RevenueCat/react-native-purchases/issues/1156
*/
const { withGradleProperties,withDangerousMod,withAndroidManifest } = require('expo/config-plugins');
const { mergeContents } = require('@expo/config-plugins/build/utils/generateCode');
const fs = require('fs/promises');
const withCrispConfig = (config) => {
// First apply the manifest modifications
config = withAndroidManifest(config,async (config) => {
const androidManifest = config.modResults;
// Add tools namespace if not present
if (!androidManifest.manifest.$) {
androidManifest.manifest.$ = {};
}
androidManifest.manifest.$['xmlns:tools'] = 'http://schemas.android.com/tools';
// Find the application node
const app = androidManifest.manifest.application[0];
if (!app['meta-data']) {
app['meta-data'] = [];
}
// Define the meta-data elements we need to modify
const metaDataElements = [
{
$: {
'android:name': 'com.google.firebase.messaging.default_notification_icon',
'android:resource': '@drawable/notification_icon',
'tools:replace': 'android:resource'
}
},
{
$: {
'android:name': 'com.google.firebase.messaging.default_notification_color',
'android:resource': '@color/notification_icon_color',
'tools:replace': 'android:resource'
}
}
];
// Update or add meta-data elements
metaDataElements.forEach(element => {
const existingIndex = app['meta-data'].findIndex(
data => data.$['android:name'] === element.$['android:name']
);
if (existingIndex >= 0) {
// Update existing element
app['meta-data'][existingIndex].$ = {
...app['meta-data'][existingIndex].$,
'tools:replace': 'android:resource'
};
} else {
// Add new element
app['meta-data'].push(element);
}
});
return config;
});
// Then apply the gradle modifications
return withDangerousMod(config,[
'android',
async (config) => {
const buildGradlePath = `${config.modRequest.platformProjectRoot}/app/build.gradle`;
const contents = await fs.readFile(buildGradlePath,'utf-8');
const packagingOptionsBlock = `
android {
packagingOptions {
resources {
pickFirsts += [
'org/commonmark/**',
'org/commonmark/internal/**',
'org/commonmark/internal/util/**',
'org/commonmark/internal/util/entities.properties',
'META-INF/LICENSE-notice.md',
'META-INF/LICENSE.md'
]
excludes += [
'META-INF/LICENSE',
'META-INF/NOTICE',
'META-INF/*.properties',
'META-INF/ASL2.0',
'META-INF/DEPENDENCIES'
]
}
}
}`;
const dependenciesBlock = `
configurations.all {
resolutionStrategy {
force 'org.commonmark:commonmark:0.21.0'
}
exclude group: 'com.atlassian.commonmark', module: 'commonmark'
exclude group: 'com.atlassian.commonmark', module: 'commonmark-ext-gfm-tables'
}
dependencies {
implementation('im.crisp:crisp-sdk:2.0.8') {
exclude group: 'com.atlassian.commonmark', module: 'commonmark'
exclude group: 'com.atlassian.commonmark', module: 'commonmark-ext-gfm-tables'
}
}`;
// Add packaging options
const contentsWithPackaging = mergeContents({
tag: 'crisp-sdk-packaging',
src: contents,
newSrc: packagingOptionsBlock,
anchor: /android\s*{/,
offset: 1,
comment: '//',
});
// Add dependencies and configurations
const newContents = mergeContents({
tag: 'crisp-sdk-dependencies',
src: contentsWithPackaging.contents || contentsWithPackaging,
newSrc: dependenciesBlock,
anchor: /dependencies\s*{/,
offset: 1,
comment: '//',
});
await fs.writeFile(buildGradlePath,newContents.contents || newContents);
return config;
},
]);
};
module.exports = withCrispConfig;
@AyoCodess I believe this solution is updating the node_modules/react-native-crisp-chat-sdk/android/build.gradle file.
if that's the case, You can use patch-package otherwise, I'm not sure of any other solutions.
Steps for using patch-package:
- install
patch-package
- NPM
npm i patch-package
- YARN
yarn add patch-package
- Update the your project's package.json file
"scripts": {
......
+ "postinstall": "patch-package"
}
- Update the
node_modules/react-native-crisp-chat-sdk/android/build.gradlefile with the fix.
implementation ('im.crisp:crisp-sdk:VERSION') {
exclude group: 'com.atlassian.commonmark', module: 'commonmark'
}
- Run patch command.
npx patch-package react-native-crisp-chat-sdk
It should patch your package, and every time anyone runs npm install, the patch will be applied to Crisp package for the same Crisp package version.
@AyoCodess I believe this solution is updating the
node_modules/react-native-crisp-chat-sdk/android/build.gradlefile.if that's the case, You can use
patch-packageotherwise, I'm not sure of any other solutions.Steps for using
patch-package:
- install
patch-package
- NPM
npm i patch-package
- YARN
yarn add patch-package
- Update the your project's package.json file
"scripts": { ...... + "postinstall": "patch-package" }
- Update the
node_modules/react-native-crisp-chat-sdk/android/build.gradlefile with the fix.implementation ('im.crisp:crisp-sdk:VERSION') { exclude group: 'com.atlassian.commonmark', module: 'commonmark' }
- Run patch command.
npx patch-package react-native-crisp-chat-sdkIt should patch your package, and every time anyone runs
npm install, the patch will be applied to Crisp package for the same Crisp package version.
Edit. My expo plugin works more reliably as there are other conflict issues with firebase apparently.
Just want to say that I'm having the same issue and while I appreciate the provided solution it feels a bit hacky.
+1 on this issue; the Crisp plugin works - but I don't love adding one-off crisp plugins.
+1 on this issue; the Crisp plugin works - but I don't love adding one-off crisp plugins.
Are you using the one i created?
How to do this properly. ?
Hi,
Could you please update to the latest version of the SDK 0.20 and let us know if the issue is still there?
Thanks!
@VirtuozTM Yes. The issue is still there with [email protected]
Duplicate class org.commonmark.node.CustomNode found in modules commonmark-0.13.0.jar -> commonmark-0.13.0 (com.atlassian.commonmark:commonmark:0.13.0) and commonmark-0.21.0.jar -> commonmark-0.21.0 (org.commonmark:commonmark:0.21.0)
Can you give us a reproductible example ?