Creating an Android Config Plugin for Expo | React Native Guide
Originally published on PEAKIQ Expo config plugins allow you to extend and customize the native behavior of your Expo app. Here, we will create a plugin that modifies various aspects of the Android project configuration, including the AndroidManifest.xml, Gradle properties, and MainApplication.java file. Prerequisites Basic knowledge of JavaScript and Node.js Understanding of Android development concepts Expo CLI installed Steps to Create the Plugin 1. Setting Up the Plugin First, ensure you have the necessary imports from @expo/config-plugins: const { withAndroidManifest, withGradleProperties, withProjectBuildGradle, withAppBuildGradle, withMainApplication } = require("@expo/config-plugins"); 2. Defining Configuration Changes We will define various configuration changes that our plugin will apply: Gradle Properties: Define new Gradle properties to be added to the project. Activities: Add new activities to the AndroidManifest.xml. Intent Data: Add new intent filters to the AndroidManifest.xml. const new_Gradle_Properties = [ { type: 'property', key: 'AsyncStorage_db_size_in_MB', value: '2048' }, { type: 'property', key: 'android.disableAutomaticComponentCreation', value: 'true' }, { type: 'property', key: 'hermesEnabled', value: 'false' } ]; const new_Activities = [ { $: { "android:name": 'com.ahmedadeltito.photoeditor.PhotoEditorActivity' } }, { $: { "android:name": 'com.yalantis.ucrop.UCropActivity' } } ]; const new_intentData = [ { $: { "android:mimeType": "*/*" } } ]; 3. Modifying MainApplication.java We will modify the MainApplication.java file to include necessary imports and update the onCreate method to change the CursorWindow size: const ModifyMainApplication = withMainApplication(config, async config => { let { contents } = config.modResults; if (!contents.includes('import static com.facebook.react.views.textinput.ReactEditText.DEBUG_MODE;')) { const packageIndex = contents.indexOf('package '); const packageEndIndex = contents.indexOf(';', packageIndex) + 1; contents = [ contents.slice(0, packageEndIndex), `\nimport static com.facebook.react.views.textinput.ReactEditText.DEBUG_MODE; import android.database.CursorWindow; import java.lang.reflect.Field;\n`, contents.slice(packageEndIndex) ].join(''); } const onCreateMethodMatch = 'super.onCreate();'; const tryCatchBlock = ` try { Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize"); field.setAccessible(true); field.set(null, 100 * 1024 * 1024); // 100MB } catch (Exception e) { if (DEBUG_MODE) { e.printStackTrace(); } } `; if (contents.includes(onCreateMethodMatch) && !contents.includes(tryCatchBlock.trim())) { contents = contents.replace( onCreateMethodMatch, `${onCreateMethodMatch}${tryCatchBlock}` ); } config.modResults.contents = contents; return config; }); 4. Modifying AndroidManifest.xml We will add new activities and intent filters to the AndroidManifest.xml: const AddActivityMain_fest = withAndroidManifest(config, async config => { const androidManifest = config.modResults.manifest; const mainApplication = androidManifest.application[0]; mainApplication.$['android:hardwareAccelerated'] = 'true'; mainApplication.$['android:largeHeap'] = 'true'; const currentActivities = mainApplication.activity; new_Activities.map(activity => { const foundActivity = currentActivities.find(anActivity => anActivity.$['android:name'] === activity.$['android:name']); if (!foundActivity) currentActivities.push(activity); }); const currIntent = androidManifest.queries[0]?.intent[0].data; new_intentData.map(intentData => { const foundIntentData = currIntent.find(anIntendData => anIntendData.$['android:mimeType'] === intentData.$['android:mimeType']); if (!foundIntentData) currIntent.push(intentData); }); return config; }); 5. Modifying Gradle Properties We will set the new Gradle properties: const SetGradleProperties = withGradleProperties(config, config => { new_Gradle_Properties.map(gradleProperty => { const foundProperty = config.modResults.find(prop => prop.key === gradleProperty.key); if (foundProperty) { foundProperty.value = gradleProperty.value; } else { config.modResults.push(gradleProperty); } }); return config; }); 6. Modifying Build Gradle We will modify the project and app build gradle files: const SetAppBuildGradleProperties = withProjectBuildGradle(config, config => { config.modResults.contents += ` subprojects { subproject -> if(project['name'] == 'react-native-reanimated'){ project.configurations { compile { } } } }`; return config; }); const SetAppBuildGradle = withAppBuildGradle(config, config => { config.modResults.contents += ` android { defaultConfig { manifestPlaceholders = [appAuthRedirectScheme: 'gopak360'] }}`; return config; }); 7. Exporting the Plugin Finally, we export the plugin by combining all the modifications: module.exports = function AndroidPlugin(config) { return Object.assign( SetGradleProperties, AddActivityMain_fest, SetAppBuildGradleProperties, SetAppBuildGradle, ModifyMainApplication ); }; Conclusion By following the steps outlined above, you can create a custom Android config plugin for Expo to manage and modify your Android project's configuration efficiently. This approach helps keep your project maintainable and flexible, allowing for easy updates and modifications. For more details and advanced usage, refer to the Expo documentation.
