PEAKIQ - Software Solutions & Digital Innovation Peakiq Software Development

Peakiq Blog

Creating an Android Config Plugin for Expo in React Native

Step-by-step guide to building a custom Android Config Plugin for Expo. Learn how to automate Android configuration changes in React Native apps safely and maintainably.

Editorial3 min read577 words
Creating an Android Config Plugin for Expo in React Native

Introduction

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.