Write an Android Studio Plugin Part 3: IntelliJ IDEA is an official recommended blog for learning about IDE plug-in development.

In Part 2 of this series, we learned how to persist data with Component and use that data to show what new features are updated after the user updates our plug-in. In today’s article, we’ll see how to use persistent data to create a Settings page.

Remember, you can find all the code for this series on GitHub, and you can also view the code for each article on the corresponding branch, which is in the Part3 branch.

Github.com/marcosholga…

What are we going to do?

The purpose of this article is to create a setup page for our plug-in, which will be the first step towards bringing JIRA here. There will be only one username and password field on our Settings page that our plug-in will use to interact with the Jira API. We also want to be able to set different Settings for each project, allowing users to use different Jira accounts for each project (which might be useful).

Step 1: Create a New Project-level Component

In Part 2 of this series, we learned what a Component is and that there are three different types of components. Since we want to be able to set things up differently for each Project in our Android Studio, the obvious choice is to create a new Project Component.

We basically copied and pasted the Component we created earlier, but removed all unnecessary methods and added two new fields. These fields will be public because we will use them in other parts of the plug-in.

Another difference is that this time we implement the ProjectComponent interface and AbstractProjectComponent method, which also has a project parameter in its constructor. Finally, we have a Companion Object that takes a project parameter to get an instance of our JiraComponent. This will enable us to access stored data from other locations in the plug-in. The new JiraComponent looks like this:

@State(name = "JiraConfiguration",
        storages = [Storage(value = "jiraConfiguration.xml")])
class JiraComponent(project: Project? = null) :
        AbstractProjectComponent(project),
        Serializable,
        PersistentStateComponent<JiraComponent> {

    var username: String = ""
    var password: String = ""

    override fun getState(a): JiraComponent? = this

    override fun loadState(state: JiraComponent) =
            XmlSerializerUtil.copyBean(state, this)

    companion object {
        fun getInstance(project: Project): JiraComponent =
                project.getComponent(JiraComponent::class.java)
    }
}
Copy the code

As we did above, we must also register the Component in the plugin.xml file:

<project-components>
    <! -- Add your project components here -->
    <component>
        <implementation-class>
            components.JiraComponent
        </implementation-class>
    </component>
</project-components>
Copy the code

Step 2: UI

Before we take the next step with our Settings page, we need to understand how to create a UI on IntelliJ using Java Swing. IntelliJ has a number of Swing components you can use to ensure that the plug-in UI is consistent with other plug-ins in the IDE. But don’t be fooled by having Java in the name, because you can still convert the code to Kotlin.

One way to create a New GUI (graphical user interface) is to simply right-click and go to New, then click GUI Form. This will create a new file called yourname.form, which will link to another file called yourname.java. Rather than follow the editor model given by IntelliJ, I prefer to go my own way and give a hint:

I’ll be using Eclipse! (Cheers)

I know what you’re thinking, but honestly, it’s great. For some reason, the IntelliJ editor was really hard to use and I couldn’t get the results I wanted, but if you’re happy with IntelliJ, keep using it!

Translator’s note: I don’t like the official IDEA editor either, but there’s no great need to use Eclipse because it’s just a preview of the UI.

Back in Eclipse, you can download it here. I currently have Oxygen. 3A, which is a bit old but not important for what we are going to do. Simply create a New project, then right-click New, Other, and select JPanel:

Here is a preview of our Settings page:

All we need to do is copy the created source code from Eclipse, and then we can close Eclipse.

Going back to our plug-in, we will now create a new package called Settings and a new class called JiraSettings. In this class, we will create a new method called createComponent(), where we can finally paste the source code copied from Eclipse. Then it’s time to convert the code to Kotlin, and you should be able to automatically convert it to Kotlin successfully.

After you have done all this, you may encounter some errors, so fix them.

The first thing we need to resolve is that our createComponent() method must return a JComponent, for reasons we’ll talk about later.

Because Eclipse assumes we’re already in a JPanel, you can see a lot of Add methods or methods that don’t seem to exist because we’re not in a JPanel. To solve this problem, we have to create a new JPanel and give it some boundaries (you can get values from the JPanel created in Eclipse), and since JPanel is a subclass of JComponent, we will return it in our methods.

In the end, we only need to make a few adjustments to compile the entire program, which should look like this:

class JiraSettings {

    private val passwordField = JPasswordField()
    private val txtUsername = JTextField()

    fun createComponent(a): JComponent {

        val mainPanel = JPanel()
        mainPanel.setBounds(0.0.452.120)
        mainPanel.layout = null

        val lblUsername = JLabel("Username")
        lblUsername.setBounds(30.25.83.16)
        mainPanel.add(lblUsername)

        val lblPassword = JLabel("Password")
        lblPassword.setBounds(30.74.83.16)
        mainPanel.add(lblPassword)

        passwordField.setBounds(125.69.291.26)
        mainPanel.add(passwordField)

        txtUsername.setBounds(125.20.291.26)
        mainPanel.add(txtUsername)
        txtUsername.columns = 10

        return mainPanel
    }
}
Copy the code

Step 3: Extensions and Extension Points

Before moving on to the Settings page, we must discuss Extensions and Extension Points. They will allow your plug-in to interact with other plug-ins or with the IDE itself.

  • If you want to extend other plug-ins orIDEYou must declare one or more functionsextensions.
  • If you want a plug-in to allow other plug-ins to extend its functionality, you must declare one or moreextension points.

Since we’re adding the Settings page to the Preferences for Android Studio, all we really need to do is extend the functionality of Android Studio, so we have to declare an Extension.

To do this, we must implement a different system and also rewrite some methods.

  • Fortunately, we already have onecreateComponent()Method, so we just addoverrideKeywords will do.
  • We’re going to create onebooleanthemodified, its default value isfalseAnd as aisModified()The return value of. We’ll talk more about this later, but for now it represents the Settings pageapplyWhether the button is enabled.
  • We will begetDisplayName()Is used to display the name of the Settings page.
  • inapplyThe method we need to write will click in the userApplyThe code that executes when. It’s very simple, we are for the userProjectTo obtainJiraComponent, and then willUISave the value toComponentIn the. And finally, we’re going toModifySet tofalseAt that time we will disable itApplyButton.

The final display effect is as follows:

class JiraSettings(private val project: Project): Configurable {

    private val passwordField = JPasswordField()
    private val txtUsername = JTextField()

    private var modified = false

    override fun isModified(a): Boolean = modified

    override fun getDisplayName(a): String = "MyPlugin Jira"

    override fun apply(a) {
        val config = JiraComponent.getInstance(project)
        config.username = txtUsername.text
        config.password = String(passwordField.password)

        modified = false
    }

    override fun createComponent(a): JComponent {

        val mainPanel = JPanel()
        mainPanel.setBounds(0.0.452.120)
        mainPanel.layout = null

        val lblUsername = JLabel("Username")
        lblUsername.setBounds(30.25.83.16)
        mainPanel.add(lblUsername)

        val lblPassword = JLabel("Password")
        lblPassword.setBounds(30.74.83.16)
        mainPanel.add(lblPassword)

        passwordField.setBounds(125.69.291.26)
        mainPanel.add(passwordField)

        txtUsername.setBounds(125.20.291.26)
        mainPanel.add(txtUsername)
        txtUsername.columns = 10

        return mainPanel
    }
}
Copy the code

Step 4: Solve the final problem

We’re almost done, just a few questions left.

First, we want to save the user’s preferences, but we haven’t loaded them yet. The UI is created in the createComponent() method, so we just need to add the following code before returning to set up the UI with previously stored values:

val config = JiraComponent.getInstance(project)
txtUsername.text = config.username
passwordField.text = config.password
Copy the code

Next, we will use isModified() to solve the problem. When the user changes any value in the Settings page, we need to somehow change the value from False to true. One very simple approach is to implement a DocumentListener, which provides three methods: changeUpdate, insertUpdate, and removeUpdate.

In these methods, the only thing we need to do is simply change the value of Modify to true, and finally add the DocumentListener to our password and username fields.

override fun changedUpdate(e: DocumentEvent?). {
    modified = true
}

override fun insertUpdate(e: DocumentEvent?). {
    modified = true
}

override fun removeUpdate(e: DocumentEvent?). {
    modified = true
}
Copy the code

The final implementation is as follows:

class JiraSettings(private val project: Project): Configurable, DocumentListener {
    private val passwordField = JPasswordField()
    private val txtUsername = JTextField()
    private var modified = false

    override fun isModified(a): Boolean = modified

    override fun getDisplayName(a): String = "MyPlugin Jira"

    override fun apply(a) {
        val config = JiraComponent.getInstance(project)
        config.username = txtUsername.text
        config.password = String(passwordField.password)
        modified = false
    }

    override fun changedUpdate(e: DocumentEvent?). {
        modified = true
    }

    override fun insertUpdate(e: DocumentEvent?). {
        modified = true
    }

    override fun removeUpdate(e: DocumentEvent?). {
        modified = true
    }

    override fun createComponent(a): JComponent {

        val mainPanel = JPanel()
        mainPanel.setBounds(0.0.452.120)
        mainPanel.layout = null

        val lblUsername = JLabel("Username")
        lblUsername.setBounds(30.25.83.16)
        mainPanel.add(lblUsername)

        val lblPassword = JLabel("Password")
        lblPassword.setBounds(30.74.83.16)
        mainPanel.add(lblPassword)

        passwordField.setBounds(125.69.291.26)
        mainPanel.add(passwordField)

        txtUsername.setBounds(125.20.291.26)
        mainPanel.add(txtUsername)
        txtUsername.columns = 10

        valconfig = JiraComponent.getInstance(project) txtUsername.text = config.username passwordField.text = config.password passwordField.document? .addDocumentListener(this) txtUsername.document? .addDocumentListener(this)

        return mainPanel
    }
}
Copy the code

Step 5: Declare extension

As with Component, we must also declare extension in the plugin.xml file.

<extensions defaultExtensionNs="com.intellij">
    <defaultProjectTypeProvider type="Android"/>
    <projectConfigurable
            instance="settings.JiraSettings">
    </projectConfigurable>
</extensions>
Copy the code

And you’re done! When debugging or installing plug-ins, you can go to Preferences/Other Settings in Android Studio and find the new Settings page. You can also use different projects for testing, and each Project remembers its own Settings.

That’s all for Part 3. In the next article, we’ll see how to use these Settings to create new actions that migrate JIRa-related functionality. In the meantime, if you have any questions, please visit Twitter or comment.


“AndroidStudio plugin” translation series

  • Create a basic plugin for AndroidStudio
  • Write a plugin for AndroidStudio (2): persist data
  • Write a plugin for AndroidStudio (3): setup page
  • Write a plugin for AndroidStudio (4): integrate Jira
  • Writing AndroidStudio plug-ins (5): localization and notifications

About the translator

If you think this article is valuable to you, welcome to ❤️, and also welcome to follow my blog or GitHub.

If you feel that the article is not enough, please also urge me to write a better article by paying attention to it — in case I make progress one day?

  • My Android learning system
  • About the article error correction
  • About paying for knowledge
  • About the Reflections series