This message was deleted.
# android
s
This message was deleted.
s
Look's like we're working on similar areas right now šŸ™‚ Instead of the quotes, I've managed to get
Copy code
add("fooImplementation",project(":login",configuration="release"))
work.
I think this should work for the debug variant too.. There is a documentation on flavor and dimensions hierarchy in android multi-module projects...lemme find it.
Copy code
<https://developer.android.com/studio/build/build-variants#variant_aware>
That's the one...providing fallbacks could be another approach for this?
a
I just tried having two new product flavors and matchingFallbacks to almost manage what I want, but if
add("fooImplementation",project(":login",configuration="release"))
works that's much better! Testing right now
Godamit, I hit the same error than previously
Copy code
The Hilt Android Gradle plugin is applied but no com.google.dagger:hilt-android dependency was found.
So weird, I don't understand how that plugin being applied is an issue when using
add()
or the other way of writing it
s
That's interesting šŸ¤” . Could you try this with a build scan or a
--stacktrace
. You could probably be hitting an issue with configuration in the other project.
I'm not sure how the dagger hilt plugin factor in here šŸ˜… . or what could possibly cause that error line to come up.
a
ah, stacktrace tells me a totally other issue: that using
buildVariantImplementation
doesn't exist
Only
productFlavorImplementation
exists I guess
Also not working, only build type works ? testing
s
Both should work..A combination of buildType and flavor creates a build-variant. Using only buildType or productFlavor..applies that config too all the sub-build-variants
I'm not sure about ā˜ļø though..Could not find any evidence for this and don't have a test project handy.
a
And how does it relate to configurations? A build-variant creates a configuration?
When I try to use configuration='debug', if I don't declare it in the module itself, Gradle tells me it doesn't exist
And using the build-variant name
sitDebug
I get the following error:
org.gradle.api.artifacts.UnknownConfigurationException: Configuration with name 'sitDebugImplementation' not found.
s
Yes, android build variant translates to a group of configurations in gradle. So a
sitDebug
build variant would lead to the following configurations being created. You can also print configurations as a part of running any gradle task.
a
By running on all configurations you mean?
s
tbh I've usually stuck to only using buildTypes..since adding flavors would confuse me on how to figure out dependencies šŸ˜… .
a
I get build types, I get product flavors, but not built variants, so weird!
I print those by
Copy code
configurations.forEach {
            println(it.name)
        }
šŸ‘ 1
s
on https://gradle-community.slack.com/archives/CJYS1DAP5/p1657106461449099?thread_ts=1657047948.022919&amp;cid=CJYS1DAP5. Yes I see the same behaviour. However once I declare that buildType in the library, it works for me.
a
Weird, I need more time to investigate. Meanwhile I'll check again the matchingFallback approach
šŸ‘ 1
Ok, two new flavors in the module, and matchingFallback in the app works like intended! I just need to copy the no-op class twice for my use case, and that can probably be solved with the sourcesets (if I could manage to find how to configure sourcesets per built variant)
x
<buildVariant>Implementation
exist but only in
afterEvaluate
because that's where the variants are computed by AGP. So if you want to use this you need to wrap your
dependencies {}
section in
afterEvaluate {}
🤯 1
You should never use
implementation(project("...", configuration = "...")
This is Gradle's old way of targeting a specific configuration in a producer module that predates the whole variant-aware publication introduced several years ago.
šŸ‘ 1
Creating flavors/build types in Android library projects and consuming them with matching flavors/build types or using fallback and selection strategies is the way to go (for android modules, for java modules you have to wire all this manually with custom attributes)
a
Thank you @Xavier Ducrohet šŸ˜„ To explain better what's the use case: • I have 3 product flavors in the main app: `sit`/`uat`/`prod` • In a
login
module, we created a special
AutoLogin
class (to help us when we develop) but we want to have it on the classpath and run that special code only in
sitDebug
and
uatDebug
build variants (for security reasons) The solution: • In that
login
module, I've created a
dev
and
prod
variants • The
autoLogin
class is placed into the
devDebug
productFlavor/source folder • In
prodDebug
and
release
productFlavor/source folders, there's a no-op empty
AutoLogin
class (copied twice for now*) •
matchingFallbacks += listOf(<http://Flavor.DEV|Flavor.DEV>)
set in the
app
module, on product flavors
sit
and
uat
does the magic! Overall I like this solution a lot, as a good infra for automating things while developping, besides having to copy twice the no-op version of the class. * I hope to be able to use
sourceSets {}
to avoid the no-op class duplication but I'm not sure yet how to do that, because I need it in 3 specific build variants our of the 4. I'll look into
afterEvaluate
for that 😊
After experimenting with
afterEvaluate
, I got this:
Copy code
afterEvaluate {

    val extra = "extra"
    val extraNoop = "extra-noop"

    fun newSrcDirs(sourceSetName: String, op: Boolean) = setOf(
        File("./src/${sourceSetName}/java"),
        File("./src/${sourceSetName}/kotlin"),
        File("./src/${if (op) extra else extraNoop}/java"),
        File("./src/${if (op) extra else extraNoop}/kotlin")
    )

    android.sourceSets.forEach { sourceSet ->
        if (sourceSet.name == "devDebug") {
            sourceSet.kotlin.srcDirs(newSrcDirs(sourceSet.name, true))
        } else if (sourceSet.name in listOf("devRelease", "prodDebug", "prodRelease")) {
            sourceSet.kotlin.srcDirs(newSrcDirs(sourceSet.name, false))
        }
    }
}
The IDE picks the right sourceSet correctly, but I get the following errors while building:
Copy code
> Task login:compileDevReleaseKotlin FAILED
e: LoginMainFragment.kt: (12, 37): Unresolved reference: AutoLogin
e: LoginMainFragment.kt: (27, 29): Unresolved reference: AutoLogin
What am I missing? Probably something related to the configurations... šŸ¤”
x
You should not tweak Sourcesets in afterEvaluate, it is too late. I will say that trying to have the same folder in multiple variants is a problem on the IDE side. This is not the recommended solution.
I will offer a slightly more complicated, but arguably better modeled, solution: In your app you have a flavor dimension with 3 values (sit/uat/prod). These represent the type of your app. In your login module, the flavors have nothing to do with the type of the app. They have to do with the type of login you need (or expose really to consumers).
Based on this, instead of creating flavors in the same dimension as the app, I would have the login module have 2 different flavors in a new dimension.
then on the app, I would consume the right flavors. So prod flavor (in dimension
app-type
) would consume the prod flavor in the dimension
login-type
, while sit/uat would consume
dev
in that same login-type dimension (replace with your own dimension names šŸ™‚ )
You can do this with
missingDimensionStrategy
. see https://developer.android.com/studio/build/build-variants
a
I think I am starting to get it but let's name it all, because I'm still confused 😊 • The
app
module has a
version
dimension with 3 values (aka product flavors):
sit
/
uat
/
prod
• The
login
module ā—¦ has a
login-type
dimension 2 values:
automatic
,
manual
ā—¦ The special code doing the automatic login sits at
/login/src/automatic/java/<package>/AutoLogin.kt
ā—¦ The no-op version, that doesn't login automatically sits at
/login/src/manual/java/<package>/AutoLogin.kt
What I would need to make happen then would be the following: •
:app:sitDebug
consumes
:login:automaticDebug
<- automatic login here •
:app:sitRelease
consumes
:login:manualRelease
•
:app:uatDebug
consumes
:login:automaticDebug
<- automatic login here •
:app:uatRelease
consumes
:login:manualRelease
•
:app:prodDebug
consumes
:login:manualDebug
•
:app:prodRelease
consumes
:login:manualRelease
Can I really just do that with
missingDimensionStrategy
? I feel like I miss the point of how to differentiate between
debug
/
release
šŸ¤”
Maybe it has do to with a combination of
missingDimensionStrategy
and
matchingFallbacks
?
x
ah because you are want different consumption for uat debug vs release, that's much more complicated.
internally in Gradle, this is 100% doable. The question is how far do you want to go using low level Gradle API šŸ™‚
a
As much as needed šŸ˜„
x
AGP's flavors, build types,
missingDimensionStrategy
ultimately just sets up attributes on
Configuration
objects.
you could manually tweak these attributes to do what you want
so the first thing I would do is setup the default behavior with something like this:
Copy code
android {
  productFlavors {
    sit {
	  missingDimensionStrategy 'login-type', 'automatic'
	}
    uat {
	  missingDimensionStrategy 'login-type', 'automatic'
	}
    prod {
	  missingDimensionStrategy 'login-type', 'manual'
	}
  }
}
(in the app module)
then you need to reach into the configuration of
uatRelease
and change things.
a
Interesting!
x
Starting with AGP 7.3, you have access to the compile/runtime
Configuration
object in the variant API: https://developer.android.com/reference/tools/gradle-api/7.3/com/android/build/api/variant/Component
for earlier versions, you'd have to manually find it by name which is less maintainable. it'd be something like
uatReleaseCompileClasspath
and
uatReleaseRuntimeClasspath
with the new API you can write:
Copy code
androidComponents {
  onVariant(selector().withName('uatRelease')) { v ->
    v.compileConfiguration...
	v.runtimeConfiguration...
  }
}
Note that
selector().withName('uatRelease')
is a shortcut and less future proof than something like
selector().withBuildType('release').withFlavor('login-type' to 'uat')
a
Looking to upgrade to 7.3 right away šŸ˜„
x
Now you have to change the value of the attributes we set up for
login-type
and this is where you need low level APIs in Gradle, and some knowledge of AGP internals
After this I'll file a bug so that we can do
missingDimensionStrategy
on variant so that you don't have to deal with this
a
That would be the best yeah
x
let me figure things a bit here
Configuration.getAttributes().attributes(Attribute<T>, T)
is the API you want to use to set an attributes
to be honest, I don't know what happens if you set it and the attribute already exists (and you're just changing the value).
now the attribute and the value...
So you want an instance of
Attribute<ProductFlavorAttr>
and you can get this with
ProductFlavorAttr.Companion.of("name of dimension")
(our attribute interfaces are part of our public API)
a
Looks good I am trying to upgrade AGP to
7.3.0-beta05
and will try those APIs right away
x
so you want to write something like:
Copy code
androidComponents {
  onVariant(selector().withName('uatRelease')) { v ->
    v.compileConfiguration.getAttributes().attribute(
	  ProductFlavorAttr.Companion.of("login-type"),
	  "manual"
	)
    v.runtimeConfiguration.getAttributes().attribute(
	  ProductFlavorAttr.Companion.of("login-type"),
	  "manual"
	)
  }
}
ah I just realized the old API actually can do
missingDimensionStrategy
on a per-variant basis. 🤦
The old variant API will be removed in AGP 9.0 which is a bit over a year away so you are safe to use that
a
Oh okay
x
you could write something like
Copy code
android {
   applicationVariants.all {
     if (name == "uatRelease") {
	   missingDimensionStrategy("login-type", "manual")
	 }
   }
}
you'll have to move to the new API in the future, but you'll be blocked until we add
missingDimensionStrategy
to it (filing the bug now)
here's the bug if you want to ⭐ it: https://issuetracker.google.com/238447946
a
Yup this works! Short and to the point, I like it šŸ˜„ The IDE doesn't like it too much, but I'll live with this!
Haven't build the app yet, running it now
x
Studio will complain if you have 2 modules that needs to consume a different variant of a 3rd module
it looks like it's the case here
you can fix it by changing the variant on ui-login, to fit whichever module you need to work on, if you have unresolved symbols
if the difference between manual and automatic is just in the implementation of a class, you won't actually have unresolved symbols and you can ignore this
a
Yeah I think that's exactly what is going on here
It works really nicely! I can't thank you enough for your help on this, it will blow some minds at work next week like it blew mine! Thank you again! šŸ™
x
happy to help!
ā¤ļø 1
a
@Xavier Ducrohet I've just realized our solution here still has an issue in release build variants only (at least sitRelease and uatRelease). I'm getting the following error:
Copy code
2022-07-14 10:00:36.993 9097-9097/? E/AndroidRuntime: FATAL EXCEPTION: main
    Process: il.co.firstdigitalbank.uat, PID: 9097
    java.lang.NoClassDefFoundError: Failed resolution of: Lil/co/firstdigitalbank/login/ui/add_email/AddEmailViewModel_HiltModules_KeyModule_ProvideFactory;
        at aj.c.h(DaggerDigitalBankApp_HiltComponents_SingletonC.java:2)
        at aj.c.a(DaggerDigitalBankApp_HiltComponents_SingletonC.java:4)
        at wi.a.a(DefaultViewModelFactories.java:2)
        at gy.b.getDefaultViewModelProviderFactory(Unknown Source:4)
        at il.co.firstdigitalbank.ui.launcher.LauncherActivity$c.m(Unknown Source:2)
        at androidx.lifecycle.s0.getValue(ViewModelProvider.kt:2)
        at il.co.firstdigitalbank.ui.launcher.LauncherActivity.onCreate(LauncherActivity.kt:3)
Only when
isMinifyEnabled = true
(which was our default in release obviously), because when
isMinifyEnabled = false
the app runs, no issues. Any idea why R8 is having an issue with using
missingDimensionStrategy
?
Upgrading to the latest R8 I found in
google()
aka
com.android.tools:r8:3.3.28
doesn't help
x
hm it should not. Can you file a bug so that the R8 team can help troubleshoot this?
a
Yes sure, will do 😊