How to use multiple res.srcDirs and override some resources with gradle
Asked Answered
W

3

19

I want to build different versions of my app based on different productFlavors, but need some degree of flexibility that I can't achieve yet.

This is my folder structure:

+src
  +main
    +java
    +res
  +base
    +java
    +res
  +custom1
    +java
    +res
  +custom2
    +res

The common code is on main (a service) and the base ui is on base (the activity). Then a custom version of the app that defines a new ui is on custom1 (new activity). This is running fine. But I need another version of the app (custom2) that uses the base ui, but changing some of the res (icons, strings or colors).

What I'm trying in my build.gradle file is:

android{
    ...
  productFlavors {
    base {
      ...
    }
    custom1 {
      ...
    }
    custom2 {
      ...
    }
  }

  sourceSets{
    custom2{
      java.srcDirs = ['src/base/java']
      res.srcDirs = ['src/custom2/res', 'src/base/res']
    }
  }
}

To specify that custom2 will use the source code and resources of base and the resources of custom2.

The problem is that I get a:

Error: Duplicate resources: <project_path>/src/base/res/values-es/strings.xml:string-en/app_name, <project_path>/src/custom2/res/values-es/strings.xml:string-en/app_name

Because app_name is defined both on base and on custom2, but my goal is to override the definition of resources in base with the ones of custom2.

I know that app_name will be overriden in main if I don't specify anything in res.srcDirs for custom2, but then all the resources from base will be unavailable.

Is the approach correct? or I'm abusing of the flexibility that Gradle offers? or there is a way to do what I'm trying to do?

Thanks in advance!

Wickedness answered 20/7, 2015 at 14:5 Comment(0)
B
16

You can't achieve it in this way.

There is a priority in resource merging:

The merged resources are coming from 3 types of sources:

  • The main resources, associated with the main sourceSet, generally located in src/main/res
  • The variant overlays, coming from the Build Type, and Flavor(s).
  • The Library Project dependencies, which contribute resources through the res entry in their aar bundle.

As described here

As mentioned above, each sourceSet can define multiple resource folders. For instance:

android.sourceSets {
   main.res.srcDirs = ['src/main/res', 'src/main/res2']
}

In this case, both resource folders have the same priority. This means that if a resource is declared in both folders, the merge will fail and an error will be reported.

The same happens in your case. You can't duplicate the same resources in this way.

Benedic answered 20/7, 2015 at 14:21 Comment(5)
Thanks for your comment. I've read that link before, but missed that last paragraph. However, that means I can't do what I want? Or maybe another approach can solve my situation?Wickedness
To be honest, in my experience you can't do it in this way. You can set icons and resources using buildConfigField and resValue in your buid.gradleBenedic
After a lot of tests, I'm afraid to conclude thay this can't be done. I think that it could be achieved by excluding the resource that I want to override, but res.exclude is not supported yet (also java.exclude isn't). So it's a matter of wait and find a dirty workaround... In any case thank you for your comments.Wickedness
I don't know in advance. I'm planning for future cases that will come (sooner or later). We develop an app that is used by various operators, and each want it's own look and feel, variying from a complete new set of activities, to a simple change of icons and/or colors. Thus the need of this degree of flexibility.Wickedness
Link in the answer is broken. Working link as of today - developer.android.com/studio/write/add-resourcesCandelabra
N
1

I was able to achieve something like this by adding another level of folders at the sibling of src, called shared, and then manually checking + assembling the necessary resources in an additional script so as to avoid duplicates.

script is available here: https://github.com/smbgood/examples/blob/master/family-resources.gradle

Resources will go into folders like this (we have 3 app 'families', IG, JB + CB):
/shared/JB/res/values/ids.xml

For each build variant we have now, there is a property in ids.xml 'family' that determines which app family to use. When building, the script examines drawables and xml files found in shared, compares them against the ones present in the variant, and then removes the overlapping items, finally writing out the needed items to an /assembled/ folder.

Nuriel answered 14/6, 2016 at 22:48 Comment(1)
If you can share solution with example project than it would be great help. I want to implement this solution badly. Thanks!Derosier
M
1

you can set one path that override res files, and there are no base res files in custom2/res.

custom2.res.srcDir=['src/custom2/res']

it will search src/custom2/res path first, if file not exist find src/main/res automatically in compiling.

Mesa answered 20/10, 2021 at 6:35 Comment(1)
This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From ReviewPremeditation

© 2022 - 2024 — McMap. All rights reserved.