How I automated creating files for a new screen with my own Android Studio Plugin

Grzegorz Matyszczak
ProAndroidDev
Published in
7 min readFeb 27, 2019

--

Photo by Danial RiCaRoS on Unsplash

If you already want to check the code or install the plugin you can go here:

Introduction

Do you work on a project that has a complex architecture, where in order to create a new screen you need to add a couple of new files with specific content? For example, working with Model-View-Presenter and Dagger, you want to add the Main screen. The files that you might need to add: MainActivity.kt, MainView.kt, MainPresenter.kt, MainPresenterImpl.kt, MainModule.kt, MainComponent.kt, activity_main.xml etc. That’s quite a lot, isn’t it? I often found my self in a situation where I was simply copying these files from other screens and just renaming them. There must be a better way!

I got inspired after attending a great talk at Droidcon London 2018 about creating Android Studio Plugins by Marcos Holgado (you can find a link to talk here). I got a proper inspiration and good tutorial on how to finally automate my work. There’s also a great series of blog posts by the same author, which cover the presentation from the conference (link here).

In this blog post, I’ll focus more on difficulties which I’ve encountered while writing my plugin and how I solved them than step-by-step instructions on how to write the whole plugin since the article from the link above is a great resource for that.

Requirements for plugin

Let’s focus now on what this plugin should actually do:

  1. The user should be able to set up files which he wants to generate for each screen. I’m assuming here that there might be two types of files: Kotlin or layout XML.
  2. Those settings should be project specific.
  3. The user should be able to easily create a template of content for each file, in those templates, he could use variables like Screen Name, Screen Element’s Name (e.g. Presenter), the base class of Android Component (e.g. Activity of Fragment) etc.
  4. The user should be able to generate these files from the right-click menu on the project structure. He will be prompt to put the package name, the name of the screen and choose the Android component (Activity of Fragment).
  5. The package name should be auto-filled, depending on from which package he triggered the action, but of course, it could be changed by him in the popup dialog.

Settings screen

Here’s what I needed to do in order to create settings screen accessible from Android Studio’s Preferences. It looks like on the screenshot below.

Screen Generator settings in Preferences

Hopefully the UI is quite straightforward. You add new files to screen elements list, you can edit its name, file name template and file type in the screen details panel. In code template panel you simply write code that you want to be generated in the file and in the sample code panel you will see generated code with all the variables replaced with sample data. All the variables have format %variableName% and you can find a list of available variables with descriptions by clicking on Help link. You can also configure Activity and Fragment’s base class in android components panel.

First I needed to create a class which implements Configurable interface and then register that class in plugin.xml, in my case I added there:

Where instance is just my settings’ class name. You can find the full source code of this screen here.

The thing that I struggled here the most was actually creating a flexible UI. You need to know that it is based on the Java Swing library, so there are actually a couple of ways. You can use IntelliJ built-in tool to create UI, go to Eclipse and create UI there with its tool and then just copy the code or just write code by yourself. I chose the last option and I am not sure now if it was the easiest way. You need to know also that there are few components that IntelliJ API provides you, like JBList, ToolbarDecorator or JBSplitter and these are the preferred ones to use instead of standard Swing components, to ensure a consistent experience for users. Here you can read more about them. There’s also a nice Kotlin DSL created to build UI more easily and that’s what I used. So for example creating a simple panel would look like this:

Where activityTextField and fragmentTextField are JTextFields which I created earlier. Here you can read more about it.

Persisting state

The second requirement was to persist the state of settings for a project. This part is quite simple, I just needed to create a class with a @State annotation which implements PersistentStateComponent and register it in plugin.xml:

And here is my component:

Watch out here for class that you want to serialize! In my case it’s Settings. It needs to have all fields marked as mutable var as well as list needs to be an instance of MutableList, otherwise it won’t serialize correctly.

New Screen dialog

Templates that you create in settings screen are used later to generate specific files. You can add new screen by clicking File -> New -> Screen from the top menu or simply by right-clicking on any package in your project structure and choosing New -> Screen. Then this dialog will appear:

New Screen dialog

Here you choose your package, name of the screen and Android component that it should extend. All these inputs will replace specific variables in the files’ templates and file names.

Firstly, let’s talk on how to insert your own action to File -> New menu. You need to create your custom class which extends AnAction and then register it in plugin.xml. Here’s the XML code:

Important here is to find the correct group-id of the menu group that you want to add your action to. I found it by looking into the file LangActions.xml, you can simply open it in your IDE with a shortcut for finding symbol Cmd+Shift+O and typing the name of it.

Now in our Action class override actionPerformed function and show there a dialog. To create a simple dialog you just need to extend from class DialogWrapper. Here is the code I wrote for my NewScreenAction.

Generate files from templates

The trickiest part of writing this plugin was actually creating files. There is a specific API delivered by IntelliJ Platform SDK for operating on the files. Basic components are VirtualFile, to represent a single file from its VirtualFileSystem, PsiFile to represent file for specific language and PsiDirectory to represent a file system directory. Those are the elements I used in my plugin and if you want to read more about them then take a look here: Virtual File System, Psi Files.

There are basically four steps we need to go through. First is to get the directory of source files (e.g. app/src/main/java) or resources files (e.g. app/src/main/res), next we need to find or create a specific subdirectory according to package name if it’s a Kotlin file or just a layout subdirectory if it’s XML. The third step is to create a PsiFile and fourth is to add it to that specific subdirectory.

To get all source roots I used this method:

As you can see it returns an array of VirtualFiles, but it will contain all the source directories like generated code, test etc. I assumed, for now, that plugin will be used in a one module project, so I just filter out VirtualFile objects which path contains words like build, test and res. This will get me the proper source root. And if I want to get resources source root, I just filter the path which contains res. Project parameter here is an object of class Project and you can get it for example in actionPerformed function of your custom AnAction, because it has an AnActionEvent object passed as a parameter, and this one has Project reference inside.

Next step, I want to convert that VirtualFile I got into PsiDirectory, so I’m using this method:

Now there are two methods on PsiDirectory that are useful: findSubdirectory(name) to get a PsiDirectory object of that subdirectory and createSubdirectory(name) if the subdirectory doesn’t exist.

The last step we need to do is to create a PsiFile and add it to that subdirectory. Here’s a snippet to do it:

Of course, it’s not exactly how I wrote the code, but it’s just to show you what blocks you need to use to build such a feature :)

Kotlin language support

And I don’t mean here writing your plugin in Kotlin! It’s about creating PsiFiles that has language type of KotlinLanguage. To have access to Kotlin Language specific classes, you need to add in your plugin.xml:

And in your build.gradle:

Auto-fill the package name

One more difficulty that I’ve encountered, is how to get the current’s focused file package name, to automatically fill it into the package name text field in new screen dialog. Turns out, there’s a simple way to do it. You can get the VirtualFile object from your AnActionEvent:

Now you have the currently focused file and by parsing its path we can get the package name.

Conclusion

I had a lot of fun with writing this plugin and who knows, maybe even some of you will find it useful in your everyday development, so there will be more than one person using it :)

You can find the full source code here:

There’s also instruction of how to install it and how to use it. If you find any bug or have some feature requests, you can leave a comment here or create an issue on Github. Enjoy! :)

--

--