Learn how to use Gradle’s modern Dataflow API to hook into the build lifecycle and add an action to your plugin.

In this section, you’ll:

  • Understand what a FlowAction is and how it improves on older APIs.

  • Create a SlackBuildFlowAction class to send a build notification.

  • Register the FlowAction in your plugin to automatically execute at the end of the build.

Step 0: Before you Begin

  1. You initialized your plugin in part 1.

  2. You added an extension to your plugin in part 2.

  3. You created a custom task in part3.

  4. You wrote a unit test in part 4.

Step 1: Understanding the Dataflow API

Dataflow Actions are Gradle’s modern, recommended way to react to the build lifecycle and handle build-wide events. They are superior to older methods like BuildListener because they are fully compatible with the Configuration Cache, leading to faster and more reliable builds.

A FlowAction is a declarative, isolated, and lazy unit of work that runs in response to specific events, such as the build finishing.

The key components of the Dataflow API are:

  • FlowScope: A service that allows you to register `FlowAction`s to be triggered under certain conditions.

  • FlowAction<T>: An interface you implement to define the actual logic that runs.

  • FlowParameters: A container for all the inputs to the FlowAction.

Step 2: Creating a Dataflow Action

Let’s create the FlowAction that will send a Slack message at the end of the build.

Create a new file named `SlackBuildFlowAction.kt` in `plugin/src/main/kotlin/org/example/` and add the following code:
Create a new file named `SlackBuildFlowAction.groovy` in `plugin/src/main/groovy/org/example/` and add the following code:
plugin/src/main/kotlin/org/example/SlackBuildFlowAction.kt
package org.example

import com.slack.api.Slack
import com.slack.api.methods.request.chat.ChatPostMessageRequest

import org.gradle.api.flow.FlowAction
import org.gradle.api.flow.FlowParameters
import org.gradle.api.logging.Logging
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input

/**
 * A Gradle FlowAction that sends a Slack message at the end of a build.
 * This leverages Gradle's build lifecycle via the dataflow API, which allows actions
 * to run automatically when the build completes—without needing to attach listeners manually.
 */
abstract class SlackBuildFlowAction : FlowAction<SlackBuildFlowAction.Params> {

    private val logger = Logging.getLogger(SlackBuildFlowAction::class.java)

    /**
     * Parameters that are passed to this action when it executes.
     * These are injected by Gradle and used to control what the action does.
     */
    interface Params : FlowParameters {
        /** Slack bot token used to authenticate API requests */
        @get:Input
        val token: Property<String>

        /** Slack channel ID or name to send the message to */
        @get:Input
        val channel: Property<String>

        /** Flag indicating whether the build failed */
        @get:Input
        val buildFailed: Property<Boolean>
    }

    /**
     * Executes the action when the build finishes.
     * Constructs and sends a Slack message indicating whether the build succeeded or failed.
     */
    override fun execute(parameters: Params) {
        // Initialize the Slack client and get the API methods interface
        val slack = Slack.getInstance()
        val methods = slack.methods(parameters.token.get())

        // Compose the message text based on the build result
        val status = if (parameters.buildFailed.get()) "Build failed" else "Build succeeded"

        // Create a Slack message request
        val request = ChatPostMessageRequest.builder()
            .channel(parameters.channel.get())
            .text(status)
            .build()

        // Send the message via the Slack API and check for success
        val response = methods.chatPostMessage(request)
        if (response.isOk) {
            logger.lifecycle("Slack message sent successfully to channel ${response.channel}")
        } else {
            logger.error("Failed to send Slack message: ${response.error}")
            throw RuntimeException("Slack message failed: ${response.error}")
        }
    }
}
plugin/src/main/groovy/org/example/SlackBuildFlowAction.groovy
package org.example

import com.slack.api.Slack
import com.slack.api.methods.request.chat.ChatPostMessageRequest

import org.gradle.api.flow.FlowAction
import org.gradle.api.flow.FlowParameters
import org.gradle.api.logging.Logging
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input

/**
 * A Gradle FlowAction that sends a Slack message at the end of a build.
 * This leverages Gradle's build lifecycle via the dataflow API, which allows actions
 * to run automatically when the build completes—without needing to attach listeners manually.
 */
abstract class SlackBuildFlowAction implements FlowAction<SlackBuildFlowAction.Params> {

    private final logger = Logging.getLogger(SlackBuildFlowAction)

    /**
     * Parameters that are passed to this action when it executes.
     * These are injected by Gradle and used to control what the action does.
     */
    interface Params extends FlowParameters {
        /** Slack bot token used to authenticate API requests */
        @Input
        Property<String> getToken()

        /** Slack channel ID or name to send the message to */
        @Input
        Property<String> getChannel()

        /** Flag indicating whether the build failed */
        @Input
        Property<Boolean> getBuildFailed()
    }

    /**
     * Executes the action when the build finishes.
     * Constructs and sends a Slack message indicating whether the build succeeded or failed.
     */
    @Override
    void execute(Params parameters) {
        // Initialize the Slack client and get the API methods interface
        def slack = Slack.getInstance()
        def methods = slack.methods(parameters.token.get())

        // Compose the message text based on the build result
        def status = parameters.buildFailed.get() ? 'Build failed' : 'Build succeeded'

        // Create a Slack message request
        def request = ChatPostMessageRequest.builder()
                .channel(parameters.channel.get())
                .text(status)
                .build()

        try {
            // Send the message via the Slack API and check for success
            def response = methods.chatPostMessage(request)
            if (response.isOk()) {
                logger.lifecycle("Slack message sent successfully to channel ${parameters.channel.get()}")
            } else {
                logger.error("Failed to send Slack message: ${response.error}")
                throw new RuntimeException("Slack message failed: ${response.error}")
            }
        } catch (Exception e) {
            logger.error("Exception while sending Slack message", e)
            throw new RuntimeException("Slack message failed", e)
        }
    }
}

This code completes two of the three main components of our flow action:

  1. The SlackBuildFlowAction class is a FlowAction that uses the Slack API client to send a message.

  2. The SlackBuildFlowAction.Params interface defines the input parameters for the action, including the token, channel, and a buildFailed flag.

Step 3: Registering the Dataflow Action in the Plugin

Now, we’ll register the flow action in our plugin’s apply() method to connect it to the build lifecycle.

Update your SlackPlugin class to look like this:

plugin/src/main/kotlin/org/example/SlackPlugin.kt
package org.example

import org.gradle.api.Project
import org.gradle.api.Plugin
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.flow.*
import javax.inject.Inject

abstract class SlackPlugin : Plugin<Project> {
    @Inject
    abstract fun getFlowScope(): FlowScope

    @Inject
    abstract fun getFlowProviders(): FlowProviders

    override fun apply(project: Project) {
        // Create the 'slack' extension so users can configure token, channel, and message
        val extension = project.extensions.create("slack", SlackExtension::class.java)

        // Register a task named 'sendTestSlackMessage' of type SlackTask
        val taskProvider: TaskProvider<SlackTask> = project.tasks.register("sendTestSlackMessage", SlackTask::class.java)

        // Configure the task using values from the extension
        taskProvider.configure {
            it.group = "notification" // Logical task grouping for help output
            it.description = "Sends a test message to Slack using the configured token and channel."

            // Bind extension values to the task's input properties
            it.token.set(extension.token)
            it.channel.set(extension.channel)
            it.message.set(extension.message)
        }

        // Hook into the build lifecycle using the dataflow API
        getFlowScope().always(SlackBuildFlowAction::class.java) { spec ->
            spec.parameters.token.set(extension.token)
            spec.parameters.channel.set(extension.channel)
            spec.parameters.buildFailed.set(getFlowProviders().buildWorkResult.map { it.failure.isPresent })
        }
    }
}
plugin/src/main/groovy/org/example/SlackPlugin.groovy
package org.example

import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.flow.*
import javax.inject.Inject

/**
 * The SlackPlugin class is a Gradle plugin that registers the 'slack' extension
 * and a task, and also hooks into the build lifecycle using a FlowAction.
 */
abstract class SlackPlugin implements Plugin<Project> {
    @Inject
    abstract FlowScope getFlowScope()

    @Inject
    abstract FlowProviders getFlowProviders()

    @Override
    void apply(Project project) {
        // Create the 'slack' extension so users can configure token, channel, and message
        def extension = project.extensions.create('slack', SlackExtension)

        // Register a task named 'sendTestSlackMessage' of type SlackTask
        TaskProvider<SlackTask> taskProvider = project.tasks.register('sendTestSlackMessage', SlackTask)

        // Configure the task using values from the extension
        taskProvider.configure {
            it.group = 'notification' // Logical task grouping for help output
            it.description = 'Sends a test message to Slack using the configured token and channel.'

            // Bind extension values to the task's input properties
            it.token.set(extension.token)
            it.channel.set(extension.channel)
            it.message.set(extension.message)
        }

        // Hook into the build lifecycle using the dataflow API
        getFlowScope().always(SlackBuildFlowAction) { spec ->
            spec.parameters.token.set(extension.token)
            spec.parameters.channel.set(extension.channel)
            spec.parameters.buildFailed.set(getFlowProviders().buildWorkResult.map { it.failure.isPresent() })
        }
    }
}

In this code, we ask Gradle to inject two services that are essential for the Dataflow API:

  • @Inject abstract fun getFlowScope(): FlowScope: This is used to register our action with the always scope, which means it will run after every build, regardless of the outcome.

  • @Inject abstract fun getFlowProviders(): FlowProviders: This service provides access to data about the build, such as whether it succeeded or failed, via the buildWorkResult provider.

By using these services, we can configure our FlowAction to run automatically at the end of the build, providing a robust and cache-compatible way to send build notifications.

Next Step: Write a Functional Test >>