пятница, мая 06, 2016

Gradle, conditionally include or exclude LeakCanary or something else

This example shows how to include/exculde LeakCanary and AndroidDevMetrics.

Note:
when LeakCanary (or AdnroidDevMetrics) is excluded, "standart" initialization code like

  MyApp
    onCreate() {
      LeakCanary.install(this);

will not be compiled: LeakCanary class will be not found.
So in this case I have to use reflection and Class.forName.


def useLeakCanary = false
def useAndroidDevMetrics = true

apply plugin: 'com.android.application'

if (useAndroidDevMetrics) {
    apply plugin: 'com.frogermcs.androiddevmetrics'
}

buildscript {
    repositories {
        mavenLocal()
        jcenter()
    }
    dependencies {
        ...
        classpath 'com.frogermcs.androiddevmetrics:androiddevmetrics-plugin:0.4'
    }
}

android {
    ...

    buildTypes {
        release {
            useLeakCanary = false
            buildConfigField "boolean", "USE_LEAK_CANARY", "false"

            buildConfigField "boolean", "USE_ANDROID_DEV_METRICS", "false"
            ...
        }

        debug {
            useLeakCanary = true
            buildConfigField "boolean", "USE_LEAK_CANARY", "$useLeakCanary"

            buildConfigField "boolean", "USE_ANDROID_DEV_METRICS", "$useAndroidDevMetrics"
        }
    }
}

dependencies {
    ...

    if (useLeakCanary) {
        debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1'
        releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
        testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
    }
}

-------------------


public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        if (BuildConfig.USE_ANDROID_DEV_METRICS) {
            AndroidDevMetricsWrapper.initWith this
        }

        if (BuildConfig.USE_LEAK_CANARY) {
            LeakCanaryWrapper.install this
        }
    }
}

-------------------

class AndroidDevMetricsWrapper {

    public static void initWith(Application app) {
        try {
            Class leakCanaryClass = Class.forName "com.frogermcs.androiddevmetrics.AndroidDevMetrics"
            Method initWith = leakCanaryClass.getMethod "initWith", Context.class
            initWith.invoke null, app
        } catch (Exception e) {
            Log.e "AndroidDevMetricsWrapper", Log.getStackTraceString(e)
        }
    }
}

public class LeakCanaryWrapper {

    public static void install(Application app) {
        try {
            Class leakCanaryClass = Class.forName "com.squareup.leakcanary.LeakCanary"
            Method init = leakCanaryClass.getMethod "install", Application.class
            init.invoke null, app
        } catch (Exception e) {
            Log.e "LeakCanaryWrapper", Log.getStackTraceString(e)
        }
    }

}


How to use custom gradle generated constant in android java code

It is really easy to define gradle-generated constants and use those constants in android java code.
For example, I want to use a build type dependent constant RETRY_COUNT.

Gradle's class BuildType has a method:
    public void buildConfigField(String type, String name, String value)

This method can be used like this:

android {
...

    buildTypes {
        release {
            buildConfigField "int", "RETRY_COUNT", "3"
...
        }

        debug {
            buildConfigField "int", "RETRY_COUNT", "10"
...
        }
    }
}

Next, I rebuild my project, open BuildConfig and see:

public final class BuildConfig {
...
public static final boolean RETRY_COUNT = 10;
}



вторник, марта 15, 2016

Custom view, EditText, проблема при восстановлении состояния (use saveEnabled(fals))

С большим интересом потратил несколько часов на локализацию следующей проблемы:
- у меня есть custom view
- это композитный view, внутри которого используется EditText
- таких view у меня на экране несколько
- custom view обрабатывает сохранение и восстановление состояния в методах onSaveInstanceState и onRestoreInstanceState
- дебаггер показывает, что вызов этих методов и их работа выполняются корректно.

Но: после смены ориентации экрана в каждом из этих view содержится текст, который был введён в самый нижний из этих view.

Решение:
для EditText, входящего в состав композитного view, запретить сохранение состояния:

<EditText
    android:id="@+id/value"
    android:saveEnabled="false"
    .../>


понедельник, февраля 29, 2016

Robolectric, getActivityInfo, NullPointerException at start

I have created a robolectric test, when I started one, I have got an exception:

java.lang.NullPointerException
at org.robolectric.res.builder.DefaultPackageManager.getActivityInfo(DefaultPackageManager.java:173)
at org.robolectric.util.ActivityController.getActivityInfo(ActivityController.java:65)
at org.robolectric.util.ActivityController.attach(ActivityController.java:51)
at org.robolectric.util.ActivityController$1.run(ActivityController.java:121)
at org.robolectric.shadows.ShadowLooper.runPaused(ShadowLooper.java:304)
at org.robolectric.shadows.CoreShadowsAdapter$2.runPaused(CoreShadowsAdapter.java:45)
at org.robolectric.util.ActivityController.create(ActivityController.java:118)
at org.robolectric.util.ActivityController.create(ActivityController.java:129)
at ru.simpls.brs2.mobbank.ui.modules.freereqpayment.ui.FreeReqPaymentActivityTest.init(FreeReqPaymentActivityTest.java:29)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:251)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:188)
at org.robolectric.RobolectricTestRunner.runChild(RobolectricTestRunner.java:54)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:152)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:78)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:212)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:68)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:140)

Process finished with exit code -1

Here is my test's declaration:

@RunWith(RobolectricGradleTestRunner.class)
@Config(
        constants = BuildConfig.class,
        sdk = 21,
        manifest = "src/main/AndroidManifest.xml")
public class FreeReqPaymentActivityTest {...}

In my project I have some modules. I have imported BuildConfig from another module, not from one where test is defined.

So when I have changed the import to a correct module, and problem was solved.