AUTO1 Group

IntelliJ IDEA plugin development - focus on UI based plugins

By Vadim Shchenev

Vadim is an Android Developer at AUTO1 Group.

< Back to list
Engineering

BACKGROUND

This post is in addition to the previous article about plugin development but with a focus on UI based plugin implementations. Sometimes it can be really useful to have an UI and make it possible to interact with it by doing some checkboxes selections or maybe to have some input areas for text. Let's investigate how to develop a simple plugin with input text area as input entry point and some new generated classes, based on input text as a result. As a source code example we will have a look at source code based on my pet project that also was used as a development tool in AUTO1 to generate non GraphQl based POJO classes.

USER STORY

As an Android developer I need to interact with backend data. It can be usually presented as JSON data or if we can speak about new and modern practices it also can be presented as a GraphQl scheme. GraphQl is a really powerful framework with its own classes generation framework. But let's go back one step and imagine that we have to do some data presentation classes updated for the project without GraphQl support. To do it, we need to find the target class, compare its fields with new JSON fields and update it. It can be easy if we have only one field changed, but for multiple inner fields and classes with its own inner fields it can be too complicated. We can find some online services to convert JSON to POJO and copy/paste output files, but let's try to integrate this technology to our IDE! And our job will be only to copy/paste new JSON to some window input field! Like this one:

ss1

To make it possible, let's think about our problem and try to do small decomposition into logical steps. Lets formulate our goals by imaging simple user stories:

  1. As a user I want to input JSON text into the input field.

  2. As a user I want to transform JSON text into POJO files.

  3. As a user I want to see the result notification.

We will skip JSON to POJO transformation for this article, but we will learn how to develop a plugin with required user interface. Let's think about option number two as about some library that we already have and can use as a part of our small project.

TECHNOLOGY STACK

Before we will start let's have a look on basis plugin implementation. There are 2 base options to develop new plugin:

We will speak about gradle implementation. Gradle is a really powerful framework and it can be really helpful if we want to simplify our building, releasing and testing. The project structure is a common structure for gradle based projects:

my_gradle_plugin

├── build.gradle

├── gradle

│ └── wrapper

│ ├── gradle-wrapper.jar

│ └── gradle-wrapper.properties

├── gradlew

├── gradlew.bat

├── settings.gradle

└── src

├── main

│ ├── java

│ └── resources

│ └── META-INF

│ └── plugin.xml

└── test

├── java

└── resources

  

Most important topic for us will be the plugin.xml file. We already discussed some details in the previous article and we can be focused on some UI related topics.

During the plugin development we can use Java/Kotlin and all utility tools that we want like jUnit testing and dependency injection frameworks. In our current example Koin and JUnit can be found. So, now we have and know about project structure, some utility tools, language to write the code and now we are ready to think how it can be possible to create a user interface and connect it with our IDE. Let's remember about our major goals:

GOAL 1 - As a user I want to input JSON text into the input field.
GOAL 2 - As a user I want to see the result notification.

and see how we can implement it.

GOAL 1

First of all, let's learn how to connect our plugin with the IDE user interface. As we can see the IDE has a lot of action groups to do something (create new files for example). According to our idea to have some logic that can help us to convert JSON to POJO it would be great to have a new item in the new file list. It will be something like an action “Create new POJO file”.

To connect our logic with the IDE by following this path we have to remember about the plugin.xml file that was discussed early. We can use this configuration file to describe our plugin and how it can be used.

Let's find the actions section and define new action to show our input field dialog view.

<actions>

<action id="GeneratePOJOAction" class="GeneratePOJOAction"

icon="/icons/pojo.png"

text="Generate POJO from JSON"

description="GeneratePOJOAction">

<add-to-group group-id="NewGroup" anchor="last"/>

</action>

</actions>

As we can see we can use some properties to describe how users will be available to interact with our plugin. We can define a custom icon for our action, define the position inside of the items list (“NewGroup” anchor “last”) and we can define the class to execute when action will be invoked by the user.

ss2

Custom action class should extend the abstract AnAction class with the implementation of actionPerformed() method.

import com.intellij.openapi.actionSystem.AnAction

import com.intellij.openapi.actionSystem.AnActionEvent

class GeneratePOJOAction : AnAction() {

override fun actionPerformed(e: AnActionEvent) { 

//TODO: Add your UI implementation here 

} 
}

Inside of actionPerformed() method implementation we can add the logic to show a dialog window with the input field for JSON text. To show dialog view we can easily use DialogBuilder from plugin development SDK and Java swing to create a user interface.

More information about actions can be found on the official documentation page.

GOAL 2

Let's imagine now that we already implemented logic to show the input text dialog with some selectable options to transform this text. We also added some logic of transformation - that can be some library to work with it or maybe our own implementation. And as the result of that transformation some new POJO files were generated added into our project (We can interact directly with the project or we can use project representation, provided by the plugin development SDK).

But what about the next steps? We also need to notify the user about the result: it can be a success result notification, or maybe some error case notification. To implement it we can use default tools, provided by plugin development SDK. We can show some information to the user by using popup, notifications or messages.

Let's imagine the following case: we have a text input field and we have some logic to convert/transform the text from this input field. If we can speak about JSON to POJO transformation, we have to understand that any random text cannot be converted to POJO correctly. Target text should satisfy some conditions and also should have the correct JSON structure and it means that we have to not only show some notification if text structure is not valid, but we have to block UI and break our transformation flow.

Messages can really help us with it! According to the official documentation:

The Messages class provides a way to show simple message boxes, input dialogs (modal dialogs with a text field), and chooser dialogs (modal dialogs with a combo box). The function of different methods of the class should be clear from their names. When running on macOS, the message boxes shown by the Messages class use the native UI.

Lets see the following code:

companion object {

private const val BUTTON_TITLE = "OK"

private const val DEFAULT_OPTION_INDEX = -1

private val DEFAULT_ICON = null

}

fun onPluginExceptionHandled(exception: RoboPluginException) {

Messages.showDialog(

exception.message, exception.title,

arrayOf(BUTTON_TITLE),

DEFAULT_OPTION_INDEX,

DEFAULT_ICON

)

}

As we can see we can use the show dialog method, provided by SDK. We can define some dialog parameters, like title, message, button and of course we can define our custom plugin icon. If the image is not defined, standart Java icon or IDE icon would be shown. More information about images support can be found here. Also as it was already discussed here we can add support for internationalization instead of hardcoding only english string values.

And as the result we can see this amazing small UI blocking window with our message text:

ss3

It looks now that we found a solution to cover the error cases. Now we can prevent users from wrong input and show notification about it. Let's think about the success use case. Let's imagine that the input text was correct and we processed our JSON to POJO transformation as expected. Maybe some new files were generated, maybe it was only one new file. It will be a really good user experience if we can find a possibility to inform our users about it. Maybe one of the possible ways can be to show notification messages. It can be a small popup view with some text, like this one:

ss4

and maybe we also can print some information to the event log:

12:00  Response.java was updated.
  
12:00  POJO generation: Success

It looks now that we are ready to totally satisfy our user story requirements! Let's do the final steps and implement the following code:

companion object{

private const val MESSAGE_SUCCESS = "POJO generation: Success"

private const val GROUP_ID = "RoboPOJO Generator"

}

fun showSuccessMessage() {

showMessage(

project = ProjectManager.getInstance().openProjects.first(),

message = MESSAGE_SUCCESS,

type = NotificationType.INFORMATION,

)

}

private fun showMessage(

message: String, type: NotificationType, project: Project

) = NotificationGroupManager.getInstance()

.getNotificationGroup(GROUP_ID)

.createNotification(message, type)

.notify(project)

  

As we can see we can use the create notification method, provided by SDK. Unlike dialog related logic, we also have to define the project that we want to use to show the notification. As an example let's get only the first project (most popular use case), but we have to understand that users can open not only one project in the IDE and we have to take care about it too. So, as we can see it is also possible to define message text. Also we can define the message type (it can be INFORMATION, WARNING or ERROR) and we have to take care about the notification group that was defined inside of our plugin.xml file:

<extensions defaultExtensionNs="com.intellij">

<notificationGroup id="RoboPOJO Generator"

displayType="BALLOON"

key="com.robohorse.robopojogenerator"/>

</extensions>

FEW WORDS AT THE END

As we can see it was not really a big deal to develop some small and hopefully useful tools for daily use or to help your teammates. The major idea is just to understand that integration with IDE can be really simple and when we will finish with it, we can be focused on the functionality of our tool. Perhaps in the future when we will have a stable version with some really cool features we can share our tool not only with teammates that we are working with directly, but maybe it also can be helpful for some developers from some random parts of the world. And maybe together we can simplify our daily job!

Stories you might like:
By Ivan Kozlov

A few words about one of the biggest Java conferences and AUTO1's presence there.