# Part 5: Add a DataFlow Action

version 9.2.0

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 >>