I’ve been trying to setup convention plugins to ce...
# gradle
r
I’ve been trying to setup convention plugins to centralize logic, and everything has been working great. But now I wanted to make them configurable (via extensions). From what I found I either had to use
afterEvaluate
or understand Gradle lifecycle to configure things at specific times, otherwise the module wouldn’t have set up things yet and it would just hit the convention values. Instead I came up with this:
Copy code
class AndroidLibraryConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            with(pluginManager) {
                apply("com.android.library")
                apply("org.jetbrains.kotlin.android")
            }

            ...

            extensions.create(AndroidLibraryExtension::class, "gst", AndroidLibraryExtensionImpl::class, target)
        }
    }
}

interface AndroidLibraryExtension {
    fun compose()
}

internal abstract class AndroidLibraryExtensionImpl(
    private val project: Project,
) : AndroidLibraryExtension {
    override fun compose() = with(project) {
        extensions.configure<LibraryExtension> {
            configureCompose(this)
        }
    }
}
Is this a common practice? Does it have any drawback I’m not thinking of? I couldn’t really find much about this topic. Thank you!
v
afterEvaluate
you should avoid as hell. Using it is most often just symptom treatment. It is like using
SwingUtilities.runLater
or
Platform.runLater
to "fix" a GUI Bug. It just shifts the problem to a later point in time where it is harder to reproduce, harder to debug, and harder to fix. The main effect of
afterEvaluate
is to introduce timing problems, ordering problems, and race conditions. If you got example read the configuration in
afterEvaluate
, but the user also uses
afterEvaluate
after you and therein does the configuration, you again miss it. There are some edge cases where it might unavoidable to use it, but in most cases it is better to use some alternative. Having a function in the extension that does some configuration is such an alternative. You don't necessarily need to split the extension into interface and implementation for that, but you can.
r
Okay that’s great to hear!
You don’t necessarily need to split the extension into interface and implementation for that, but you can.
I couldn’t find any method that would let me pass constructor params and a single type, so I guess I would just pass the same type in both?
Copy code
extensions.create(AndroidLibraryExtension::class, "gst", AndroidLibraryExtension::class, target)
v
No you don't need to give it twice. Iirc if you want to specify it explicitly you might need to annotate the conductor with
@Inject
. But actually, you can just leave out the explicit constructor argument. For
Project
on an extension constructor it just automagically works: https://github.com/gradle/gradle/issues/27427
r
Ah, that’s a bit magical, but it worked fine. Thank you for your help! 👍
👌 1