📜 ⬆️ ⬇️

Total standardization



When a dog programmer has nothing to do, he begins to automate everything. By the nature of my work, I have to write a lot of code and, of course, I want to summarize some repeating things in the form of libraries, scripts or templates for Android Studio. We will talk about them.


')

Templates - who are they?


A template in terms of Android Studio is a file (or set of files) with the .ftl extension, containing constructs in Java and XML (depending on the problem to be solved), as well as metadesign in the language of the template engine (template engine). In our case, the template engine is FreeMarker , whose language is simple, but at the same time powerful enough to write complex templates.

We already have a bunch of different templates at our disposal: activity, fragments, services, widgets, UI components, directories, and more. But there are cases (such as ours) when existing templates are not enough and you need to make your own. And here we are comprehended by the first “surprise”: there is very little information on this case. There are several articles on blogs and miraculously extracted poor documentation from GoogleGit.

Inspection of the territory


It is best to learn from examples, so we will analyze a fairly simple template, but at the same time containing all important aspects of the empty fragment template. You can get it from ANDROID_STUDIO_DIR / plugins / android / lib / templates / other / BlankFragment . It is better to copy the contents of this directory somewhere, so as not to break anything with your own experiments. First, let's deal with what is there.


Understanding what happens in each of the files is easy. Problems begin when there is a need to customize what is happening in them or even even to write something from scratch. This may help a little official documentation.

And where are the real examples ?!


Understanding the nuances of the BlankFragment template is only half the battle. To consolidate the knowledge gained, you need to do something of your own. Those who are too lazy to invent their own options can take mine. The rest are looking and inspired.

In our applications, we use the MVP architecture, according to which each fragment is a View, and any View needs a Presenter. Let's set aside everything that concerns the Model-layer and calculate the classes:

At first glance, not so much. But if we put this into practice, everything turns out not so rosy. It is necessary to create a fragment and markup to it, register this markup in the fragment, design a View-interface, and implement this interface as empty methods. The same will have to be done with the Presenter. But it still needs to be scattered in packages, you do not want your project to be a one-level hell for classes, right? You also need to link View and Presenter, which also requires writing code with your hands. With all this, of course, you can live, but such a routine is quickly annoying. This is where templates come to the rescue. With their help, we automate all these routine actions to create architectural pieces.

Through hardship to the stars


Let's start with the implementation of the template.xml file. You see its contents in beautiful windows when creating an activity, fragments, etc. We will follow the simplest path and create our MVP template based on the existing EmptyActivity template. We copy this directory to ourselves, rename it to MVPActivity and convert the template.xml file to the following form:

template.xml
<?xml version="1.0"?> <template format="5" revision="1" name="MVP Activity" minApi="7" minBuildApi="14" description="Creates a new MVP activity"> <category value="MVP" /> <formfactor value="Mobile" /> <parameter id="activityClass" name="Activity Name" type="string" constraints="class|unique|nonempty" suggest="${layoutToActivity(layoutName)}" default="MainActivity" help="The name of the activity class to create" /> <parameter id="generateLayout" name="Generate Layout File" type="boolean" default="true" help="If true, a layout file will be generated" /> <parameter id="generateView" name="Generate View" type="boolean" default="true" help="If true, a View interface will be generated" /> <parameter id="generatePresenter" name="Generate Presenter?" type="boolean" default="true" help="If true, a Presenter interface will be generated" /> <parameter id="generatePresenterImpl" name="Generate Presenter implementation?" type="boolean" default="true" help="If true, a Presenter implementation will be generated" /> <parameter id="layoutName" name="Layout Name" type="string" constraints="layout|unique|nonempty" suggest="${activityToLayout(activityClass)}" default="activity_main" visibility="generateLayout" help="The name of the layout to create for the activity" /> <parameter id="isLauncher" name="Launcher Activity" type="boolean" default="false" help="If true, this activity will have a CATEGORY_LAUNCHER intent filter, making it visible in the launcher" /> <parameter id="viewName" name="View Name" type="string" constraints="class|nonempty|unique" default="MainView" visibility="generateView" suggest="${underscoreToCamelCase(classToResource(activityClass))}View" help="The name of the View interface to create" /> <parameter id="presenterName" name="Presenter Name" type="string" constraints="class|nonempty|unique" default="MainPresenter" visibility="generatePresenter" suggest="${underscoreToCamelCase(classToResource(activityClass))}Presenter" help="The name of the Presenter interface to create" /> <parameter id="presenterImplName" name="Presenter Implementation Name" type="string" constraints="class|nonempty|unique" default="MainPresenterImpl" visibility="generatePresenterImpl" suggest="${underscoreToCamelCase(classToResource(activityClass))}PresenterImpl" help="The name of the presenter implementation class to create" /> <parameter id="packageName" name="Package name" type="string" constraints="package" default="com.mycompany.myapp" /> <!-- 128x128 thumbnails relative to template.xml --> <thumbs> <!-- default thumbnail is required --> <thumb>template_blank_activity.png</thumb> </thumbs> <globals file="globals.xml.ftl" /> <execute file="recipe.xml.ftl" /> </template> 


To understand the nuances of the official documentation will help you, but I will focus on the main differences. First, the following blocks were added:

  <parameter id="generateView" name="Generate View" type="boolean" default="true" help="If true, a View interface will be generated" /> 

Which will be displayed in the form of checkboxes, allowing you to enable / disable the generation of certain components. Secondly, the fields for entering the names of the classes and interfaces of the View and Presenters were added:

  <parameter id="viewName" name="View Name" type="string" constraints="class|nonempty|unique" default="MainView" visibility="generateView" suggest="${underscoreToCamelCase(classToResource(activityClass))}View" help="The name of the View interface to create" /> 

Be sure to pay attention to the visibility attribute. This is the id of the checkbox that is responsible for displaying this field. The main magic happens in the attribute attribute. Here we remove all possible underscores, cut off the 'Activity' suffix and add our 'View' suffix. With the rest of the contents of this file, I think, questions should arise.

Now it is the turn of patterns of architectural components. As we remember, it is necessary to locate all this in the root / src directory. We organize the catalog as follows:

After that, you can do directly template files. Let's start with SimpleActivity.java.ftl , which we inherited from the base template. It must be moved to the ui / activity directory and brought to the following form:

SimpleActivity.java.ftl
 package ${packageName}.ui.activity; import ${superClassFqcn}; import android.os.Bundle; import ${packageName}.R; <#if generateView>import ${packageName}.presentation.view.${viewName};</#if> <#if generatePresenter>import ${packageName}.presentation.presenter.${presenterName};</#if> <#if generatePresenterImpl>import ${packageName}.presentation.implementation.${presenterImplName};</#if> public class ${activityClass} extends ${superClass} <#if generateView>implements ${viewName}</#if>{ <#if generatePresenter> private ${presenterName} mPresenter; </#if> @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); <#if generateLayout> setContentView(R.layout.${layoutName}); </#if> <#if generatePresenterImpl> mPresenter = new ${presenterImplName}(this); </#if> } <#if generatePresenter> @Override protected void onDestroy() { mPresenter.onDestroy(); super.onDestroy(); } </#if> } 


The main difference from the source file is that the implementation of the View interface is added, as well as the creation and destruction of the Presenter is added. Since we previously made these things optional, this was reflected in the template code as <#if> ... </ # if> conditions.

Now the problem is a bit more complicated: write a template file from scratch. But do not worry, the previously obtained knowledge is more than enough to solve this problem. You should have something like this:

SimpleView.java.ftl
 package ${packageName}.presentation.view; public interface ${viewName} { } 


SimplePresenter.java.ftl
 package ${packageName}.presentation.presenter; public interface ${presenterName} { void onDestroy(); } 


SimplePresenterImpl.java.ftl
 package ${packageName}.presentation.implementation; import ${packageName}.presentation.view.${viewName}; import ${packageName}.presentation.presenter.${presenterName}; public class ${presenterImplName} implements ${presenterName} { private ${viewName} mView; public ${presenterImplName}(final ${viewName} view) { mView = view; } @Override public void onDestroy() { mView = null; } } 


Of course, each of these files must be in its package. Now that the ingredients are ready, we need a recipe for making all this good.

recipe.xml.ftl
 <?xml version="1.0"?> <recipe> <#include "../common/recipe_manifest.xml.ftl" /> <#if generateLayout> <#include "../common/recipe_simple.xml.ftl" /> <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" /> </#if> <instantiate from="root/src/app_package/ui/activity/SimpleActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/ui/activity/${activityClass}.java" /> <open file="${escapeXmlAttribute(srcOut)}//ui/activity/${activityClass}.java" /> <#if generateView> <instantiate from="root/src/app_package/presentation/view/SimpleView.java.ftl" to="${escapeXmlAttribute(srcOut)}/presentation/view/${viewName}.java" /> <open file="${escapeXmlAttribute(srcOut)}/presentation/view/${viewName}.java" /> </#if> <#if generatePresenter> <instantiate from="root/src/app_package/presentation/presenter/SimplePresenter.java.ftl" to="${escapeXmlAttribute(srcOut)}/presentation/presenter/${presenterName}.java" /> <open file="${escapeXmlAttribute(srcOut)}/presentation/presenter/${presenterName}.java" /> </#if> <#if generatePresenterImpl> <instantiate from="root/src/app_package/presentation/implementation/SimplePresenterImpl.java.ftl" to="${escapeXmlAttribute(srcOut)}/presentation/implementation/${presenterImplName}.java" /> <open file="${escapeXmlAttribute(srcOut)}/presentation/implementation/${presenterImplName}.java" /> </#if> </recipe> 


Unlike the original file, the optional instantiation of our architectural components is added here. We will leave the globals.xml.ftl file unchanged, and you can draw the picture yourself or take mine.

The end


At this, the creation of the template can be considered complete. It's time to look at the result. To do this, go to ANDROID_STUDIO_DIR / plugins / android / lib / templates / activities and copy the directory with our MVPActivity template into it. We start the studio, go to File -> New, look for a new MVP category, open our template from it and rejoice in the result.

And for those who for some reason missed the links in the text of the article, I give them here as a general list:

Source: https://habr.com/ru/post/274897/


All Articles