react-native-crisp-chat-sdk icon indicating copy to clipboard operation
react-native-crisp-chat-sdk copied to clipboard

react-native-purchase-ui common-mark duplicate classes error (Dependency clash)

Open AyoParadis opened this issue 11 months ago • 12 comments

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?

AyoParadis avatar Jan 04 '25 21:01 AyoParadis

Have you already tried this?

https://github.com/RevenueCat/purchases-android/issues/1667#issuecomment-2063155867

raghav-phonebox avatar Jan 07 '25 17:01 raghav-phonebox

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?

AyoParadis avatar Jan 07 '25 17:01 AyoParadis

@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;

AyoParadis avatar Jan 08 '25 08:01 AyoParadis

@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:

  1. install patch-package
  • NPM
npm i patch-package
  • YARN
yarn add patch-package
  1. Update the your project's package.json file
 "scripts": {
    ......
+  "postinstall": "patch-package"
 }
  1. Update the node_modules/react-native-crisp-chat-sdk/android/build.gradle file with the fix.
implementation ('im.crisp:crisp-sdk:VERSION') {
  exclude group: 'com.atlassian.commonmark', module: 'commonmark'
}
  1. 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.

raghav-phonebox avatar Jan 08 '25 18:01 raghav-phonebox

@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:

  1. install patch-package
  • NPM

npm i patch-package

  • YARN

yarn add patch-package

  1. Update the your project's package.json file

 "scripts": {

    ......

+  "postinstall": "patch-package"

 }

  1. Update the node_modules/react-native-crisp-chat-sdk/android/build.gradle file with the fix.

implementation ('im.crisp:crisp-sdk:VERSION') {

  exclude group: 'com.atlassian.commonmark', module: 'commonmark'

}

  1. 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.

Edit. My expo plugin works more reliably as there are other conflict issues with firebase apparently.

AyoParadis avatar Jan 08 '25 18:01 AyoParadis

Just want to say that I'm having the same issue and while I appreciate the provided solution it feels a bit hacky.

alextoul avatar Jan 20 '25 14:01 alextoul

+1 on this issue; the Crisp plugin works - but I don't love adding one-off crisp plugins.

wesvance avatar Jan 20 '25 21:01 wesvance

+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?

AyoParadis avatar Jan 20 '25 23:01 AyoParadis

How to do this properly. ?

manutheblacker avatar Aug 17 '25 10:08 manutheblacker

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 avatar Sep 18 '25 15:09 VirtuozTM

@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)

vishalpawar048 avatar Sep 28 '25 09:09 vishalpawar048

Can you give us a reproductible example ?

VirtuozTM avatar Oct 17 '25 12:10 VirtuozTM