Write an Android Studio Plugin Part 2: Persisting Data is a series of blog posts that persist in IDE studio development and are recommended by IntelliJ IDEA.

In the first part of this series, we learned how to create a basic plug-in for Android Studio and wrote our first Action. In this article, you’ll learn how to persist data in plug-ins.

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 Part2 branch.

Github.com/marcosholga…

What are we going to do?

Our goal today is to persist the data in the plug-in. Along the way, we’ll learn what Component is and how to use component to manage the plug-in lifecycle. We’ll start using it next to ensure that notifications are displayed when Android Studio launches and a new version of the plug-in is installed. You can use this feature in the future to show users what your plug-in has updated.

What is Component?

Before writing any code, we need to understand what Component is. Component is the basic concept of plug-in integration. Component allows us to control the life cycle of plug-ins and maintain their state so that they can be saved and loaded automatically.

There are three different types of Components:

  • ApplicationLevel:IDE (Android Studio)Created and initialized at startup timeComponent;
  • ProjectLevel: Created for each project instanceComponent;
  • ModuleLevel: Created for modules in each projectCompoenent.

Step 1: Create a New Component

First we need to determine the type of Component we need. In this article, we want to do something when Android Studio starts, so by looking at the different types of Components, we can clearly see that we need an Application-level Component.

The official JetBrains documentation states that we can implement the ApplicationComponent interface, which is optional, but we will do so in this article. The ApplicationComponent interface will give us the previously mentioned lifecycle methods that we can use to perform certain actions when the IDE starts. These methods come from BaseComponent, an extension of ApplicationComponent.

public interface BaseComponent extends NamedComponent {
  /**
   * Component should perform initialization and communication with other components in this method.
   * This is called after {@link com.intellij.openapi.components.PersistentStateComponent#loadState(Object)}.
   */
  default void initComponent(a) {}/ * * *@see com.intellij.openapi.Disposable
   */
  default void disposeComponent(a) {}}Copy the code

Now let’s code Component. The first iteration will be simple. We check for a new version by inheriting ApplicationComponent and overriding initComponent.

class MyComponent: ApplicationComponent {

    override fun initComponent(a) {
        super.initComponent()
        if (isANewVersion()) { }
    }

    private fun isANewVersion(a): Boolean = true

}
Copy the code

Step 2: Implement isANewVersion()

So here’s the fun part. We’ll start by declaring two new fields: localVersion and Version. The first will store the most recent version we have installed, while the second will be the actual installed version of our plug-in.

I want to compare them to check if the version number localVersion is after Version, so that we know if the user has just installed the new version of the plug-in, and if we send notifications to the user. We also have to update localVersion to the same value as version so that the next time a user launches Android Studio, they no longer receive the welcome message.

First, the user did not install our plug-in, so we set the localVersion value to 0.0 because our first release was 1.0-snapshot. You can change the version of the plug-in in the build.gradle file, but if not, it should:

version 1.0 the SNAPSHOT ' '
Copy the code

To implement isANewVersion, we’ll do a few simple things. You can change it if you want, but this is a simple comparison for version numbers, so I don’t cover the whole logic of boundary conditions.

First, I got rid of the -snapshot part that WAS actually maintained on the version. After that, I split each version number using. As a separator. So we get a List

that contains all the primary and secondary numbers so that we can compare them and return true or false. It’s not the best algorithm, but it’s good enough for our needs. The algorithm assumes that the two version numbers must have the same length and must follow the same naming convention majorv. minorV.

private fun isANewVersion(a): Boolean {
    val s1 = localVersion.split("-") [0].split(".")
    val s2 = version.split("-") [0].split(".")

    if(s1.size ! = s2.size)return false
    var i = 0

    do {
        if (s1[i] < s2[i]) return true
        i++
    } while (i < s1.size && i < s2.size)

    return false
}
Copy the code

Step 3: Integrate

Now it’s time to move the isANewVersion method and the two new fields into Component. You can use PluginManager to get the plug-in version and the plug-in ID, and you can also find (and change) the plug-in ID in the plugin.xml file. I also recommend changing the plug-in name to something more meaningful at this point, such as My Awesome Plugin.

<idea-plugin>
    <id>myplugin.myplugin</id>
    <name>My awesome plugin</name>.Copy the code

The entire implementation of this Component is very simple: We get the version from the PluginManager, check to see if any version updates have occurred, and if so, update the local version number synchronously so that the whole process is not triggered the next time Android Studio starts. Finally, we show the user a simple notification that, when integrated, looks something like this.

class MyComponent: ApplicationComponent {

    private var localVersion: String = "0.0"
    private lateinit var version: String

    override fun initComponent(a) {
        super.initComponent()

        version = PluginManager.getPlugin(
            PluginId.getId("myplugin.myplugin"))!!!!! .versionif (isANewVersion()) {
            updateLocalVersion()
            val noti = NotificationGroup("myplugin",
                                         NotificationDisplayType.BALLOON,
                                         true)

            noti.createNotification("Plugin updated"."Welcome to the new version",
                                   NotificationType.INFORMATION,
                                   null)
                .notify(null)}}private fun isANewVersion(a): Boolean {
        val s1 = localVersion.split("-") [0].split(".")
        val s2 = version.split("-") [0].split(".")

        if(s1.size ! = s2.size)return false
        var i = 0

        do {
            val l1 = s1[i]
            val l2 = s2[i]
            if (l1 < l2) return true
            i++
        } while (i < s1.size && i < s2.size)

        return false
    }

    private fun updateLocalVersion(a) {
        localVersion = version
    }

}
Copy the code

Step 4: Persist Component state

Now we can try to test our plug-in, which doesn’t work properly for two reasons.

First because we still have to register Component, and second because we haven’t really preserved the state of Component. Every time Android Studio initializes, the localVersion value is still 0.0.

So what do we do about saving Component state? To do this, we must implement a PersistentStateComponent. This means we have to override two new methods, getState and loadState. We also need to understand how PersistentStateComponent works, which stores public fields, annotated private fields, and bean properties in XML format. To remove a public field from the serialization, annotate it with @TRANSIENT.

In our example, I’ll use the @attribute annotation localVersion to store it while keeping it private, return the component itself to getState and load the State with XmlSerializerUtil, we must also annotate the component with @state, And specify the location of the XML.

@State(
        name = "MyConfiguration",
        storages = [Storage(value = "myConfiguration.xml")])
class MyComponent: ApplicationComponent.PersistentStateComponent<MyComponent> {

    @Attribute
    private var localVersion: String = "0.0"
    private lateinit var version: String

    override fun initComponent(a) {
        super.initComponent()

        version = PluginManager.getPlugin(
                PluginId.getId("myplugin.myplugin"))!!!!! .versionif (isANewVersion()) {
            updateLocalVersion()
            val noti = NotificationGroup("myplugin",
                                         NotificationDisplayType.BALLOON,
                                         true)
            noti.createNotification("Plugin updated"."Welcome to the new version",
                                    NotificationType.INFORMATION,
                                    null)
                .notify(null)}}override fun getState(a): MyComponent? = this

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

    private fun isANewVersion(a): Boolean {
        val s1 = localVersion.split("-") [0].split(".")
        val s2 = version.split("-") [0].split(".")

        if(s1.size ! = s2.size)return false
        var i = 0

        do {
            if (s1[i] < s2[i]) return true
            i++
        } while (i < s1.size && i < s2.size)

        return false
    }

    private fun updateLocalVersion(a) {
        localVersion = version
    }

}
Copy the code

As always, you can customize as needed, such as defining storage locations or customizing XML formats; see here for more information.

Step 5: Register Component

The last step is to register the Component in the plugin.xml file. To do this, we simply create a new Component in the application-Component section of the file and specify the Component class we just created.

<application-components>
    <component>
        <implementation-class>
            components.MyComponent
        </implementation-class>
    </component>
</application-components>
Copy the code

And you’re done! You can now run the buildPlugin command to install the plug-in from disk using the JAR files generated in Android Studio, and you’ll see this notification the next time you open Android Studio. Later, notifications will appear again only when you upgrade the plug-in version.

Part 2 ends here. In Part 3, we’ll learn how to create a Settings page for the plug-in, and before that, of course, we’ll learn how to create a UI for the plug-in.

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
  • Writing a plugin for AndroidStudio (iii): more configuration
  • 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