androidbuild-variant

How can I combine 2 Similar Projects With Android Build Variant?


We do have 2 similar projects for an app. The first one is for my country and the second one is for the EU. They are %90 same may be more. We do want to merge these two projects into one and switch between them with build variants. Is that possible? If it is, how can I do it?

Main Project

@OnClick(R.id.image_view)
    public void onClicked(View view) {
        if (isXEnabled) {
            ImageView.setImageResource(R.drawable.inactive);
            ImageView.setContentDescription(getString(R.string.passive));
            presenter.sendCommand();
            isEnabled = false;
        } else {
            ImageView.setImageResource(R.drawable.active);
            ImageView.setContentDescription(getString(R.string.active));
            presenter.disableFeature();
            isEnabled = true;
        }
    }

EU Project

@OnClick(R.id.image_view)
    public void onClicked(View view) {
        if (isXEnabled) {
            ImageView.setImageResource(R.drawable.EuInactive);
            ImageView.setContentDescription(getString(R.string.EUpassive));
            presenter.sendCommand();
            isEnabled = false;
        } else {
            ImageView.setImageResource(R.drawable.active);
            ImageView.setContentDescription(getString(R.string.EUactive));
            presenter.disableFeature();
            isEnabled = true;
        }
    }

For example, some drawable is different on EU Project

- Or -

Main Project

@OnClick(R.id.image_view)
    public void onClicked(View view) {
        if (isXEnabled) {
            ImageView.setImageResource(R.drawable.inactive);
            ImageView.setContentDescription(getString(R.string.passive));
            presenter.sendCommand();
            isEnabled = false;
            DoSomething();
        } else {
            ImageView.setImageResource(R.drawable.active);
            ImageView.setContentDescription(getString(R.string.active));
            presenter.disableFeature();
            isEnabled = true;
        }
    }

Eu Project

@OnClick(R.id.image_view)
    public void onClicked(View view) {
        if (isXEnabled) {
            ImageView.setImageResource(R.drawable.inactive);
            ImageView.setContentDescription(getString(R.string.passive));
            presenter.sendCommand();
            isEnabled = false;
            DoSomethingElse();
        } else {
            ImageView.setImageResource(R.drawable.active);
            ImageView.setContentDescription(getString(R.string.active));
            presenter.disableFeature();
            isEnabled = true;
        }
    }

In this example we did added different line everything else is similar.

There nearly a thousand of this kind of differences and nearly five hundreds of exactly same classes and two hundreds of new classes.

In conclusion, how can I manage these two projects with build variants in one project?

Edit 2 : I've tried these steps.

  1. Implement dimensions
  2. Implement flavor
  3. Created res,asset,java files for eu project
  4. Created sourceSet in gradle for eu project

for now build variant looks like this mainDevDebug,EuDevDebug,... etc main...... ones works fine but i had an error for Eu..... ones

Error message while rebuilding or running app:

Could not determine the dependencies of task ':app:compileEuTestDebugJavaWithJavac'.
> Could not resolve all task dependencies for configuration ':app:EuTestDebugCompileClasspath'.
   > Could not resolve project :com.xx.core.main.core.
     Required by:
         project :app
      > No matching configuration of project :com.xx.core.main.core was found. The consumer was configured to find an API of a component, as well as attribute 'com.android.build.api.attributes.BuildTypeAttr' with value 'debug', attribute 'project' with value 'Eu', attribute 'default' with value 'Test', attribute 'org.jetbrains.kotlin.platform.type' with value 'androidJvm' but:
          - None of the consumable configurations have attributes.

Solution

  • From the code you've posted and from the comments, I believe you'd be able to achieve what you're looking for with product flavors read more here.

    in your APP-level build.gradle, you'd do something like this :

    android {  
        flavorDimensions 'default'
        productFlavors {
    
            foo{
                dimension 'default'
            }
    
            bar{
                dimension 'default'
            }
    
        }
    }
    

    this will define two different product flavors for you, specifically foo and bar. after doing a gradle sync, you'll see you now have these available as build variants.

    enter image description here

    next what you'll do, is create a folder structure similar to this: enter image description here

    creating a folder for bar, a folder for foo and leaving the folder for main, with the package signature inside each folder matching what your main structure is using. if you have image resources which will be different, make sure they're in appropriate folders here. If you're struggling to create a specific type of file/folder, I'd suggest just copying it over.

    everything inside main will be used in BOTH types of apps, so all the logic which is the same, will remain there. everything which can be different will reside in the folders for bar and for foo. In my case, a simple class called Example:

    class Example {
    
        fun giveValue(context: Context): String {
            return context.getString(R.string.value)
        }
    }
    

    the implementation for both of these classes are exactly the same in both folders, although yours could (and will) be entirely different.

    You'll also see I have added strings.xml into my product flavors (in your case, perhaps this would be image resources as well), I've added the following string:

    <resources>
        <string name="value">from bar</string>
    </resources>
    

    into the strings.xml found under the bar directory and

    <resources>
        <string name="value">from foo</string>
    </resources>
    

    into the strings.xml found under the foo directory

    my Example class therefore just returns whatever this value is based on the build variant

    and that's it.

    inside my main activity:

      val result = Example().giveValue(this)
            Log.v("testing", result) 
    

    so, depending on the build variant i'm using (either fooDebug or barDebug) the output will be different.