61

I know that I can use debugCompile to only pull in a dependency for the debug build. Is there a good, streamlined way to do the code initialization that is required as well? Without the dependencies the other variants will fail to compile.

theblang
  • 10,215
  • 9
  • 69
  • 120
  • it is a bit complex, because you need to a/ start stetho as your app start and b/ configure your http client to transmit to stetho. You can do that by having specific versions of your classes in the debug and release source folders, I don't find that ideal, but it works – njzk2 May 11 '15 at 16:01
  • 3
    Off the cuff, in the `debug` sourceset, create a custom `Application` class that either extends `Application` (if you're not extending it elsewhere) or extends your custom `Application` (if you are using one in your main code. Register that `Application` in `android:name` of `` in the `debug` manifest. In the case where you're using a custom `Application` normally, you'll probably need a `tools:replaceNode` attribute as well to tell the `debug` sourceset to replace what's in the `main` sourceset's manifest. Haven't tried this yet, though. – CommonsWare May 11 '15 at 16:11

4 Answers4

97

Check the @Tanis answer.

Also you can use something like this:

Add the library only on debug version.

dependencies {
   debugCompile 'com.facebook.stetho:stetho:1.1.1      
 }

In your Application you can do :

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.onCreate();
    StethoUtils.install(this);
  }
}

Then you can create different StethoUtils class in the debug/release version.

In src/debug/java/

public class StethoUtils{

   public static void install(Application application){
       Stetho.initialize(
          Stetho.newInitializerBuilder(application)
            .enableDumpapp(Stetho.defaultDumperPluginsProvider(application))
            .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(application))
            .build());

   }
}

In src/release/java/

public class StethoUtils{

   public static void install(Application application){
      // do nothing
   }
}
Gabriele Mariotti
  • 320,139
  • 94
  • 887
  • 841
  • Note: If you declare any custom build types you will need to create a StethoUtils class for it as well. E.g. src//java/ – bmul Jul 07 '17 at 13:52
  • 5
    This means you have to also include debug/release versions of your OkHttp interceptor code. – miguel Jul 21 '17 at 19:35
60

You have a few options.

Option 1: Include Stetho for all builds (using compile instead of debugCompile) and only initialize it in your Application class for debug builds.

This is pretty easy to do. In your Application class, check BuildConfig.DEBUG like so:

if (BuildConfig.DEBUG) {
    Stetho.initialize(
            Stetho.newInitializerBuilder(this)
                    .enableDumpapp(Stetho.defaultDumperPluginsProvider(this))
                    .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this))
                    .build()
    );
}

Option 2: Only include Stetho for debug builds, and create different Application classes for debug and release builds.

Thanks to Gradle, applications can have different source sets for different build variants. By default, you have release and debug build types, so you can have three different source sets:

  • debug for code you only want in debug builds
  • release for code you only want in release builds
  • main for code you want in all builds

Your application code is likely all currently in the main source set. You can simply create a new folder called debug next to the main folder in your application and mirror the structure of your main folder for everything you want to add for debug builds.

In this case, you want an Application class in your main source set that doesn't reference Stetho at all.

Then you want an Application class in your debug source set that initializes Stetho like you normally would.

You can see an example of this setup in the Stetho sample. Specifically, here's the main Application class, and here's the debug Application class. Also note that they set up manifests in each source set that selects which Application class to use.

Bryan Herbst
  • 66,602
  • 10
  • 133
  • 120
  • 3
    AFAIK, the first approach won't work with `debugCompile`, as that code would be in `main` and the dependency won't exist in `main`. – CommonsWare May 11 '15 at 16:16
  • 1
    Your are correct- that's why I said "Include Stetho for all builds-" I should have been more clear that this meant using `compile` instead of `debugCompile`. Thanks! – Bryan Herbst May 11 '15 at 16:26
  • 1
    I personally prefer the second method because your stetho code might not end at just enabled it. You might want to use dumpapps, network binding and you don't want to ship untested/debug code on production. – Vikram Bodicherla May 24 '15 at 16:20
  • 1
    Option 1 is not desirable, since you include library files but just not using them. – Jemshit Dec 12 '15 at 09:57
  • in a import ...steho... wrtie a especific annotation? – Codelaby Nov 29 '16 at 13:19
  • I really liked the sound of Option #2, however, I get a build failure `error: duplicate class` – Someone Somewhere Nov 15 '17 at 13:00
  • the solution is to move the specific source file **out** of `main` folder and duplicate it in `debug` and `release` folders (or whatever the names of your buildtypes is) together with the necessary folder structure. Now, when you switch build types you can edit the two files. – Someone Somewhere Nov 15 '17 at 13:18
8

Using java reflection may be a perferct idea:

private void initStetho() {
            if (BuildConfig.DEBUG) {
                try {
                   Class<?> stethoClazz = Class.forName("com.facebook.stetho.Stetho");
                    Method method = stethoClazz.getMethod("initializeWithDefaults",Context.class);
                    method.invoke(null, this);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }

then we can debug compile stetho:

debugCompile 'com.facebook.stetho:stetho:1.5.0'
ThomsonStan
  • 359
  • 3
  • 6
  • 1
    Probably don't want to be catching all of those exceptions or loading a class with a string (package name could change). Much cleaner solution is to use gradle and inject a debug variant when needed. – Aceofspadez44 Jul 28 '17 at 18:10
  • 1
    I believe the reflection method is the most easiest one and guaranteeing that no Stetho code is in a release build without writing code around the curch – Patrick Feb 08 '18 at 17:18
  • Just make sure you Proguard the class so that it exists even once minified. – EpicPandaForce May 02 '19 at 21:00
  • why would one use proguard in debug, and if a rare case I don't think you will need stetho – Ultimo_m May 06 '19 at 13:05
0

There is more cardinal method to connect stetho or any other lib only to debug builds - using reflection: 1) Connect your lib via debugImplementation - debugImplementation 'com.facebook.stetho:stetho:1.5.1' 2) Implement class with only static members - DynamicClassUtils:

public class DynamicClassUtils {
private static final String TAG = "DynamicClassUtils";

public static void safeInvokeStaticMethod(String fullClassName, String methodName, Class<?>[] types, Object... args) {
    try {
        Class<?> aClass = Class.forName(fullClassName);
        Method aMethod = aClass.getMethod(methodName, types);
        aMethod.invoke(null, args);
    } catch (Throwable e) {
        if (BuildConfig.DEBUG) {
            Log.e(TAG, "Error when invoking static method, message: " + e.getMessage() + ", class: " + e.getClass());
            e.printStackTrace();
        }
    }
}

public static <T> T safeGetInstance(String fullClassName, Object... args) {
    try {
        ArrayList<Class<?>> formalParameters = new ArrayList<>();
        for (Object arg : args) {
            formalParameters.add(arg.getClass());
        }
        Class<?> aClass = Class.forName(fullClassName);
        Constructor<?> ctor = aClass.getConstructor(formalParameters.toArray(new Class<?>[0]));
        return (T) ctor.newInstance(args);
    } catch (Throwable e) {
        if (BuildConfig.DEBUG) {
            Log.e(TAG, "Error when creating instance, message: " + e.getMessage());
            e.printStackTrace();
        }
        return null;
    }
}

3) use that class to init stetho and its network interceptor:

        if (BuildConfig.DEBUG) {
        Class<?>[] argTypes = new Class<?>[1];
        argTypes[0] = Context.class;
        DynamicClassUtils.safeInvokeStaticMethod("com.facebook.stetho.Stetho", "initializeWithDefaults", argTypes, this);
    }

        if (BuildConfig.DEBUG) {
        Interceptor interceptor = DynamicClassUtils.safeGetInstance("com.facebook.stetho.okhttp3.StethoInterceptor");
        if (interceptor != null) client.addNetworkInterceptor(interceptor);
    }