creative-kit-sample icon indicating copy to clipboard operation
creative-kit-sample copied to clipboard

React Native Expo

Open csirak opened this issue 3 years ago • 8 comments

Objects return as null

csirak avatar Feb 23 '22 22:02 csirak

Did you create a config plugin to get it to work? @csirak1528

ansh avatar Dec 12 '22 10:12 ansh

no. it does not work with expo

csirak avatar Mar 14 '23 00:03 csirak

I created one and it works

ansh avatar Mar 14 '23 00:03 ansh

where is it?

csirak avatar Mar 14 '23 01:03 csirak

@ansh are you able to share your config plugin?

BarakChamo avatar Sep 12 '23 00:09 BarakChamo

This is @ansh plugin from another thread: https://github.com/expo/config-plugins/issues/153

77zv avatar Jul 13 '24 16:07 77zv

For ios, I made the following plugin and it seems to work. https://docs.snap.com/snap-kit/creative-kit/Tutorials/react-native snapchat-plugin.js

module.exports = function withSnapchat(
  config,
  { snapchatClientId},
) {
  if (!config.ios) {
    config.ios = {};
  }
  if (!config.ios.infoPlist) {
    config.ios.infoPlist = {};
  }

  config.ios.infoPlist["LSApplicationQueriesSchemes"] = [
    "snapchat",
    "bitmoji-sdk",
    "itms-apps",
  ];
  config.ios.infoPlist["SCSDKClientId"] = snapchatClientId;
  config.ios.infoPlist["SCSDKRedirectUrl"] = "refer to creative kit react native docs for this";
  config.ios.infoPlist["URL Types / Document Role"] = "Editor";
  config.ios.infoPlist["URL Types / URL Identifier"] = "refer to creative kit react native docs for this";
  config.ios.infoPlist["URL Types / URL Schemes "] ="refer to creative kit react native docs for this";

  return config;
};

app.config.ts

...
plugins: [
    ["./snapchat-plugin", { snapchatClientId: "Public OAuth 2.0 Client ID" }],
    ...
    ]

77zv avatar Jul 14 '24 00:07 77zv

Here is mine. You have to replace com.example and PUBLIC_CLIENT_ID

Here is my config plugin. This works and is updated. You have to compile it to JS though.

```ts
import {
  AndroidConfig,
  createRunOncePlugin,
  ConfigPlugin,
  withProjectBuildGradle,
  withAndroidManifest,
  XML,
  withDangerousMod,
} from "@expo/config-plugins";
import {
  createGeneratedHeaderComment,
  MergeResults,
  mergeContents,
  removeGeneratedContents,
} from "@expo/config-plugins/build/utils/generateCode";
import { ExpoConfig } from "expo/config";
import * as fs from "fs-extra";
const { addMetaDataItemToMainApplication, getMainApplicationOrThrow } = AndroidConfig.Manifest;

// Fork of config-plugins mergeContents, but appends the contents to the end of the file. Taken from https://github.com/expo/expo/blob/master/packages/expo-camera/plugin/src/withCamera.ts
function appendContents({
  src,
  newSrc,
  tag,
  comment,
}: {
  src: string;
  newSrc: string;
  tag: string;
  comment: string;
}): MergeResults {
  const header = createGeneratedHeaderComment(newSrc, tag, comment);
  if (!src.includes(header)) {
    // Ensure the old generated contents are removed.
    const sanitizedTarget = removeGeneratedContents(src, tag);
    const contentsToAdd = [
      // @something
      header,
      // contents
      newSrc,
      // @end
      `${comment} @generated end ${tag}`,
    ].join("\n");

    return {
      contents: sanitizedTarget ?? src + contentsToAdd,
      didMerge: true,
      didClear: !!sanitizedTarget,
    };
  }
  return { contents: src, didClear: false, didMerge: false };
}

const PUBLIC_CLIENT_ID = "your-client-id-here";
const ANDROID_PACKAGE_NAME = "com.example.app";

const pkg = require("@snapchat/snap-kit-react-native/package.json");

const addSnapkitImport = (src: string): MergeResults => {
  return appendContents({
    tag: "expo-snapkit-import",
    src,
    newSrc: `allprojects { repositories { maven { url "https://storage.googleapis.com/snap-kit-build/maven" } } }`,
    comment: "//",
  });
};
const withSnapkitGradle: ConfigPlugin = (config) => {
  return withProjectBuildGradle(config, (config) => {
    if (config.modResults.language === "groovy") {
      config.modResults.contents = addSnapkitImport(config.modResults.contents).contents;
    } else {
      throw new Error(
        "Cannot add Snapkit maven gradle because the project build.gradle is not groovy"
      );
    }
    return config;
  });
};

async function addMetaDataToAndroidManifest(
  config: Pick<ExpoConfig, "android">,
  androidManifest: AndroidConfig.Manifest.AndroidManifest
): Promise<AndroidConfig.Manifest.AndroidManifest> {
  const name = "com.snapchat.kit.sdk.clientId";
  const value = PUBLIC_CLIENT_ID;
  const mainApplication = getMainApplicationOrThrow(androidManifest); // Get the <application /> tag and assert if it doesn't exist.
  addMetaDataItemToMainApplication(
    mainApplication,
    name, // value for `android:name`
    value // value for `android:value`
  );
  return androidManifest;
}
const withCustomMetaData: ConfigPlugin = (config) => {
  return withAndroidManifest(config, async (config) => {
    // Modifiers can be async, but try to keep them fast.
    config.modResults = await addMetaDataToAndroidManifest(config, config.modResults);
    return config;
  });
};

function addProviderToAndroidManifest(androidManifest: AndroidConfig.Manifest.AndroidManifest) {
  const app = AndroidConfig.Manifest.getMainApplicationOrThrow(
    androidManifest
  ) as AndroidConfig.Manifest.ManifestApplication & { provider?: any[] };
  // Add the provider if it doesn't exist.
  if (!app.provider) {
    app.provider = [];
  }
  // if the provider doesn't have the FileProvider, add it.
  if (!app.provider.some((p) => p.$["android:name"] === "androidx.core.content.FileProvider")) {
    // <provider android:authorities="com.example.fileprovider" android:name="androidx.core.content.FileProvider" android:exported="false" android:grantUriPermissions="true"><meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"/></provider>
    app.provider.push({
      $: {
        "android:name": "androidx.core.content.FileProvider",
        "android:authorities": `${ANDROID_PACKAGE_NAME}.fileprovider`,
        "android:exported": "false",
        "android:grantUriPermissions": "true",
      },
      "meta-data": {
        $: {
          "android:name": "android.support.FILE_PROVIDER_PATHS",
          "android:resource": "@xml/file_paths",
        },
      },
    });
  }
  return androidManifest;
}
function withProvider(config: ExpoConfig) {
  return withAndroidManifest(config, async (config) => {
    config.modResults = addProviderToAndroidManifest(config.modResults);
    return config;
  });
}

function addPackageToQuery(androidManifest: AndroidConfig.Manifest.AndroidManifest) {
  // <package android:name="com.snapchat.android" />
  const PACKAGE_NAME = "com.snapchat.android";
  const packageToAdd = {
    package: {
      $: {
        "android:name": PACKAGE_NAME,
      },
    },
  };
  // @ts-ignore since queries does exist but Expo's types don't have it.
  const queries = androidManifest.manifest.queries;
  if (!queries) {
    // @ts-ignore since queries does exist but Expo's types don't have it.
    androidManifest.manifest.queries = [...packageToAdd];
  } else {
    // @ts-ignore
    const existingQuery = androidManifest.manifest.queries[0];
    const existingPackage = existingQuery.package;
    if (existingPackage) {
      const alreadyExists = existingPackage.some(
        (pkg: any) => pkg.$["android:name"] === PACKAGE_NAME
      );
      if (alreadyExists) return androidManifest;
      existingPackage.push(packageToAdd.package);
    } else {
      existingQuery.package = [
        {
          $: { ...packageToAdd.package.$ },
        },
      ];
    }
  }

  return androidManifest;
}
function withPackage(config: ExpoConfig) {
  return withAndroidManifest(config, async (config) => {
    config.modResults = addPackageToQuery(config.modResults);
    return config;
  });
}

async function writeResXml(root: string) {
  const dir = "android/app/src/main/res/xml";
  fs.ensureDir(dir); // ensures that the directory exists
  // <?xml version="1.0" encoding="utf-8"?>
  // <paths xmlns:android="http://schemas.android.com/apk/res/android">
  //   <files-path name="files" path="." />
  //   <external-files-path name="external_files" path="." />
  //   <external-path name="external_files" path="." />
  //   <cache-path name="cached_files" path="." />
  //   <external-cache-path name="cached_files" path="." />
  //   <root-path name="root" path="." />
  // </paths>
  const xmlObj = {
    paths: {
      $: {
        "xmlns:android": "http://schemas.android.com/apk/res/android",
      },
      "files-path": {
        $: {
          name: "files",
          path: ".",
        },
      },
      "external-files-path": {
        $: {
          name: "external_files",
          path: ".",
        },
      },
      "external-path": {
        $: {
          name: "external_files",
          path: ".",
        },
      },
      "cache-path": {
        $: {
          name: "cached_files",
          path: ".",
        },
      },
      "external-cache-path": {
        $: {
          name: "cached_files",
          path: ".",
        },
      },
      "root-path": {
        $: {
          name: "root",
          path: ".",
        },
      },
    },
  };

  // this will rewrite the file if it exists. // TODO: fix this.
  await XML.writeXMLAsync({ path: `${dir}/file_paths.xml`, xml: xmlObj });
}
function withWrittenResXml(config: ExpoConfig) {
  return withDangerousMod(config, [
    "android",
    async (config) => {
      await writeResXml(config.modRequest.projectRoot);
      return config;
    },
  ]);
}

const withSnapchatSdk: ConfigPlugin = (config) => {
  return withWrittenResXml(
    withPackage(withProvider(withCustomMetaData(withSnapkitGradle(config))))
  );
};

export default createRunOncePlugin(withSnapchatSdk, pkg.name, pkg.version);

ansh avatar Jul 14 '24 00:07 ansh