Conditionally include dev client modules on Android

After being successful in creating build configurations with and without the custom development client for IOS (see this thread), I wanted to achieve the same thing on the Android side of things.

Current progress

In order to do this, I went through the changes made to the MainActivity and MainApplication files and tried to find obvious ways to make the implementation optional. My initial thinking was to add build flavors, where one flavor would contain a USE_EXPO variable that could be used to create conditions in the code. This is what I came up with:

MainActivity.java
package com.company.appname;

import android.content.Intent;

import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
 
import expo.modules.devlauncher.DevLauncherController;
import expo.modules.devmenu.react.DevMenuAwareReactActivity;

public class MainActivity extends DevMenuAwareReactActivity {
  @Override
  public void onNewIntent(Intent intent) {
+    if (DevLauncherController.tryToHandleIntent(this, intent) && BuildConfig.USE_EXPO) {
      return;
     }
    super.onNewIntent(intent);
  }
  /**
   * Returns the name of the main component registered from JavaScript. This is used to schedule
   * rendering of the component.
   */
  @Override
  protected String getMainComponentName() {
    return "AppName";
  }

  @Override
  protected ReactActivityDelegate createReactActivityDelegate() {
+    if (BuildConfig.USE_EXPO) {
      return DevLauncherController.wrapReactActivityDelegate(this, () -> new ReactActivityDelegate(this, getMainComponentName()) {
        @Override
        protected ReactRootView createRootView() {
          return new ReactRootView(MainActivity.this);
        }
      });
+    } else {
+      return new ReactActivityDelegate(this, getMainComponentName()) {
+        @Override
+        protected ReactRootView createRootView() {
+          return new ReactRootView(MainActivity.this);
+        }
+      };
    }
  }
}

MainApplication.java
package com.company.appname;

import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

import expo.modules.devlauncher.DevLauncherController;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost =
      new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
          return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          // Packages that cannot be autolinked yet can be added manually here, for example:
          // packages.add(new MyReactNativePackage());
          return packages;
        }

        @Override
        protected String getJSMainModuleName() {
          return "index";
        }
      };

  @Override
  public ReactNativeHost getReactNativeHost() {
    return mReactNativeHost;
  }

  @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);
+    if(BuildConfig.USE_EXPO) {
      DevLauncherController.initialize(this, getReactNativeHost());
+     }
    initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
  }

  /**
   * Loads Flipper in React Native templates. Call this in the onCreate method with something like
   * initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
   *
   * @param context
   * @param reactInstanceManager
   */
  private static void initializeFlipper(
      Context context, ReactInstanceManager reactInstanceManager) {
    if (BuildConfig.DEBUG) {
      try {
        /*
         We use reflection here to pick up the class that initializes Flipper,
        since Flipper library is not available in release mode
        */
        Class<?> aClass = Class.forName("com.company.appname.ReactNativeFlipper");
        aClass
            .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
            .invoke(null, context, reactInstanceManager);
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      } catch (NoSuchMethodException e) {
        e.printStackTrace();
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      } catch (InvocationTargetException e) {
        e.printStackTrace();
      }
    }
  }
}

I realize that this is most likely a naive way of accomplishing my goal, but it works! Almost. There are (at least) two issues remaining.

Issues

To begin with, the app still includes all modules needed for the custom client, which increases build time and bundle size.

Additionally, running the app with the USE_EXPO variable set to false still seems to trigger this bottom sheet:

Question

What would be the right thing to do here? I have tried many search queries and have been unable to find a scenario where this has been implemented before, which actually surprised me given how convenient it would be to have it set up this way (at least IMHO).

Any tips and/or advice would be more than welcome!

Kind Regards,
Malte