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

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

Open
AyoCodess opened this issue Jan 4, 2025 · 5 comments

Comments

@AyoCodess
Copy link

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?

@raghav-phonebox
Copy link

Have you already tried this?

RevenueCat/purchases-android#1667 (comment)

@AyoCodess
Copy link
Author

AyoCodess commented Jan 7, 2025

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?

@AyoCodess
Copy link
Author

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

@raghav-phonebox
Copy link

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

@AyoCodess
Copy link
Author

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

thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants