expo / expo/config-plugins

Snapchat SnapKit config plugin

Open
#153 5 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
TypeScript
Stars
567
Forks
136
PR merge metrics
No merged PRs in 30d

Description

### Library

https://www.npmjs.com/package/@snapchat/snap-kit-react-native

### Summary

A Snapchat SnapKit config plugin would be great! Would allow us to use Snapchat's React Native package and it's forgo its complex setup process. (https://docs.snap.com/snap-kit/creative-kit/Tutorials/react-native#step-2-update-your-androidmanifestxml-file)

I already managed to get a config plugin sort of working. Can't figure out how to do step 3 (Step 3: Define Paths in Your res/xml/filre_paths.xml File) within the config plugin

### Any existing examples?

```ts
import {
AndroidConfig,
createRunOncePlugin,
IOSConfig,
withXcodeProject,
withAppBuildGradle,
ConfigPlugin,
withStringsXml,
withProjectBuildGradle,
withAndroidManifest,
} from "@expo/config-plugins";
import {
createGeneratedHeaderComment,
MergeResults,
mergeContents,
removeGeneratedContents,
} from "@expo/config-plugins/build/utils/generateCode";
import { ExpoConfig } from "expo/config";
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 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,
androidManifest: AndroidConfig.Manifest.AndroidManifest
): Promise {
const name = "com.snapchat.kit.sdk.clientId";
const value = "{{YOUR API KEY HERE}}";
const mainApplication = getMainApplicationOrThrow(androidManifest); // Get the 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"] === "android.support.v4.content.FileProvider")
) {
//
app.provider.push({
$: {
"android:name": "android.support.v4.content.FileProvider",
"android:authorities": "${applicationId}.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) {
//
const packageToAdd = {
package: {
$: {
"android:name": "com.snapchat.android",
},
},
};
// @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 since queries does exist but Expo's types don't have it.
// TODO: this will break if there are other tags in the queries.
androidManifest.manifest.queries[0].package = {
$: { ...packageToAdd.package.$ },
};
}

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

const withSnapchatSdk: ConfigPlugin = (config) => {
return withPackage((withCustomMetaData(withSnapkitGradle(config))));
// skipping withProvider as filePath was giving me issues.
// return withPackage(withProvider(withCustomMetaData(withSnapkitGradle(config))));
};

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

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.