What’s your flavor?

product_flavors

Product Flavor is a very powerful feature. It’s the most useful way to work with different hosts, icons, package names or even makes free and paid versions of your app depending on various versions of the same app.

What is Product Flavors?

According to Google:
“You can customize product flavors to use different code and resources while sharing and reusing the parts that are common to all versions of your app.”

How can you define it?

You must write it on module-level build.gradle file inside the android block.

  productFlavors {
    free {
      applicationId 'com.example.myapp.free'
    }
    paid {
      applicationId 'com.example.myapp.paid'
    }
  }

After you create and configure your product flavars, click on Sync Now. after the sync completes, Gradle automatically creates build variants based on your build types and product flavars, and names them according to (product-flavor)(Build-Type).

screenshot-from-2017-02-13-14-50-02

Let’s see some useful examples

Multiple package names

What if you want to have installed on your phone one app with development state and one for production state. The only thing you have to do is define it like below:

  productFlavors {
    devel {
      applicationId "com.example.myapp.devel"
    }
    prod {
      applicationId "com.example.myapp"
    }
  }

or if you have two different versions like demo version and full version:

  productFlavors {
    demo {
      applicationIdSuffix ".demo"
      versionNameSuffix "-demo"
    }
    full {
      applicationIdSuffix ".full"
      versionNameSuffix "-full"
    }
  }

Use multiple hosts

We should define HOST variable:

  productFlavors {
    devel {
      buildConfigField 'String', 'HOST', '"http://37.61.202.252:8580"'
    }
    prod {
      buildConfigField 'String', 'HOST', '"http://37.61.202.252:8580"'
    }
  }

We will try to show you how you can integrate this with Retrofit to send requests to the appropriate server without handling which server you’re pointing and based on the flavor:

retrofit = new retrofit2.Retrofit.Builder()
        .baseUrl(BuildConfig.HOST)
        .client(okHttp)
        .build();

Like the above example you can define different variables in Gradle and then use it in java code, check the below snippet:

productFlavors {
    devel {
      buildConfigField 'int', 'FOO', '52'
      buildConfigField 'String', 'FOO_STRING', '"bar"'
      buildConfigField 'boolean', 'LOG', 'true'
    }
    prod {
      buildConfigField 'int', 'FOO', '42'
      buildConfigField 'String', 'FOO_STRING', '"foo"'
      buildConfigField 'boolean', 'LOG', 'false'
    }
  }

You can use them in your java code:

    Integer foo = BuildConfig.FOO;
    String fooString = BuildConfig.FOO_STRING;
    boolean log = BuildConfig.LOG;

Bonus: BuildConfig.java

You can find all information and data relating to the Build Variant in BuildConfig.class, This class created automatically for each Build Variant by Gradle. You can find this file in build directory or with Ctrl+N shortcut in AndroidStudio.

buildconfig

Declare Dependencies

You can configure a dependency for a specific build variant or even testing source set.

dependencies {
    freeCompile project(":mylibrary")
    fullReleaseCompile project(path: ':library', configuration: 'release')
    fullDebugCompile project(path: ':library', configuration: 'debug')
    testCompile 'junit:junit:4.12'
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
}

Build with source sets

Do you want to use some mocking objects in your tests? or you don’t want to use full version files or classes in the demo version? Let’s see how we can do it.

First of all, You should add ‘demo’ and ‘full’ product flavors into build.gradle file then click on Sync Now button in notification bar, after syncing you should put the real files for full version into /src/full/ path and put the demo files for demo version into /src/demo/ path. Gradle will find the corresponding files and will add them to the output package. let’s see it in code:

My build.gradle file:

  productFlavors {
    demo {
    }
    full {
    }
  }

Now we have 4 different build variants:

screenshot-from-2017-03-06-20-30-44

We need an interface class like below in /src/main/java/com/hanihashemi/test path:

public interface iApplicationCoreValue {
  void doImportantJob();
}

We should implement it for demo and full version.

For full version we should implement ApplicationCoreValue in /src/full/java/com/hanihashemi/test path

public class ApplicationCoreValue implements iApplicationCoreValue {
  @Override
  public void doImportantJob() {
    // show message ==> Please buy the full version 
  }
}

For demo version we should implement ApplicationCoreValue in /src/demo/java/com/hanihashemi/test path

public class ApplicationCoreValue implements iApplicationCoreValue {
  @Override
  public void doImportantJob() {
    // do it here
  }
}

YESSS it’s done, by choosing releaseFull we can build full version and by chossing releaseDemo we can build it for demo version.

If you think I missed something, please leave me a comment 😉

For a more in-depth and technical description of Build Variants, check out the Android Studio Build Variant user guide:
Configure Build Variants

I encourage you to read this tutorial as well:
Android Testing Codelab

Espresso, Using Intent Extra

Espresso

As you know for creating a new Espresso test in JUnit 4 style you should use ActivityTestRule to reduce the amount of boilerplate code you need to write. By using it, the testing framework launches the activity under test before each test method annotated with @Test and @before.

@RunWith(AndroidJUnit4.class)
public class UserProfileTest {

  @Rule
  public ActivityTestRule<UserProfileActivity> mActivityTestRule = new ActivityTestRule<>(UserProfileActivity.class);

  @Test
  public void signUpActivityTest() {
    ...
  }
}

So what if you want to send Intent to an activity? Here is the example:

@RunWith(AndroidJUnit4.class)
public class UserProfileTest {

  @Rule
  public ActivityTestRule<UserProfileActivity> mActivityTestRule = new ActivityTestRule<>(UserProfileActivity.class) {
    @Override
    protected Intent getActivityIntent() {
      Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
      Intent intent = new Intent(context, UserProfileActivity.class);
      intent.putExtra("ID", "120");
      return intent;
    }
  };

  @Test
  public void signUpActivityTest() {

  }
}