📜 ⬆️ ⬇️

Android development automation


Greetings, habrozhiteli. I am an Android developer. More precisely not even the case. I'm a lazy Android developer. For more than a dozen projects, I began to be oppressed by routine and mechanical operations that did not require any mental effort from me. Now the IDE does that for me.


Introduction


At the moment there are several ways to facilitate the life of the android developer. The first for me was RoboJuice with its annotations for initializing the View. At first glance, everything is good, readability does not spoil much. The problems started when it became necessary for me to inherit an Activity from some third-party to get the necessary functionality. But since all Activities are inherited from RoboActivity, this has turned into a set of crutches. After that, to similar libraries, I developed sound skepticism. AndroidAnnotations, ButterKnife, etc., can all be attributed to this group. All of them either generate a lot of their own over the code, or do some very trivial things. For example, in ButterKnife:

@InjectView(R.id.title) TextView title; 

instead
')
 title = (TextView) findViewById(R.id.title); 

When using autocomplete and quickfix it will take exactly the same amount of time. And if there is no difference - why pay more to add another library to the project.

The second is the use of IDE templates ( http://habrahabr.ru/post/183502/ ) I have not personally tried it, but most likely it requires the development of a certain habit.

Other solutions


Over the last month, I was tormented by one question - why no one has yet made a script / plugin /% its own% option for generating code from xml layout. The first thing I decided to google on this topic. The only thing I found was http://code.google.com/p/android-code-generator-plugin/ . A simple plugin for Eclipse that parses xml and makes% name% Activity.java based on it. The developer also has a blog article. Everything else was either generation in the browser, or the questions "Where can I get it?".

Having run it a couple of times, I found a number of things that did not suit me:
- initialization of the view directly in onCreate ();
- writing package for manual generation in general settings;
- no custom fonts are set for all View. By default, this is Roboto, so that the application looks the same on all versions of Android.
- implementation of Listeners at the level of Activity. When their total number exceeds 3-4, it becomes very difficult to read the code;
- generation of all View that have id. I do not see the point, since any container (for example, LinearLayout) will rarely be used in code.
- there is no generation of a simple ArrayAdapter with described Holder. The thing is quite simple, but takes time;
- judging by commits, the project is developing very slowly.

After futile attempts to search for more, I realized - you have to write yourself.

As a result of brief reflections and communication with colleagues, the following technical specifications were made:
- automatically generate packages based on the project manifest, if they do not exist;
- generate code only for those View, which can set the font or action processing;
- select 3 methods for initializing view, fonts, listeners. If all this is written together in onCreate in large projects, you get a wall of code;
- if there is a ListView in xml, make a dialog with an offer to select an xml file with the layout of the sheet element to generate an adapter.

The above described project was taken as the basis, since it already contains some ready-made solutions. For example, this method selects from the xml all View, which has been registered id.
 private NodeList getNodesWithId(InputStream inputStream) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); // factory.setNamespaceAware(true); // never forget this! DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(inputStream); XPathFactory pathFactory = XPathFactory.newInstance(); XPath xPath = pathFactory.newXPath(); XPathExpression expression = xPath.compile("//*[@id]"); return (NodeList) expression.evaluate(doc, XPathConstants.NODESET); } 

Then three classes were created: ViewGenerator, ActivityGenerator, AdapterGenerator.
Each of these classes contains string patterns for different pieces of code that are specific to each.
For example:

 private final static String FIELD_PATTERN = "\tprivate %1$s %2$s;\n"; private final static String METHOD_VOID_PATTERN = "\tprivate void %1$s(){\n%2$s\t}\n"; 

ViewGenerator generates all the code associated with the initialization of View from xml (the declaration of fields, findViewById, setTypeFace, setListener and all the necessary imports). And for both Activity and ViewHolder adapter. For example, generation for setListeners:
 private String getListeners(boolean innerClass){ StringBuilder builder = new StringBuilder(); for (WidgetResource widgetResource : widgetsTypes.keySet()) { if (BUTTON_WIDGETS.contains(widgetsTypes.get(widgetResource).getName())) { builder.append(String.format(innerClass ? ONCLICK_INNER_PATTERN : ONCLICK_PATTERN, widgetResource.getVariableName())); } } return builder.toString(); } 

ActivityGenerator generates Activity code using ViewGenerator generation:
 public String generate() { StringBuilder stringBuilder = new StringBuilder(); if (packageName != null && !packageName.isEmpty()) { stringBuilder.append(getPackage()); stringBuilder.append("\n"); } stringBuilder.append(getImports()); stringBuilder.append(viewGenerator.getImports()); stringBuilder.append("\n"); StringBuilder innerBuilder = new StringBuilder(); innerBuilder.append(getTag()); innerBuilder.append("\n"); innerBuilder.append(viewGenerator.getFields(false)); innerBuilder.append("\n"); innerBuilder.append(getCreateMethod()); innerBuilder.append("\n"); innerBuilder.append(getInitActionBarMethod()); innerBuilder.append("\n"); innerBuilder.append(viewGenerator.getInitViewsMethod(false)); innerBuilder.append("\n"); innerBuilder.append(viewGenerator.getSetFontsMethod(false)); innerBuilder.append("\n"); innerBuilder.append(viewGenerator.getSetListenersMethod(false)); stringBuilder.append(String.format(HEADER_PATTERN, activityResource.getVariableName(), innerBuilder.toString())); return stringBuilder.toString(); } 


AdapterGenerator respectively generates the Adapter code:
 public String generate() { StringBuilder stringBuilder = new StringBuilder(); if (packageName != null && !packageName.isEmpty()) { stringBuilder.append(getPackage()); stringBuilder.append("\n"); } stringBuilder.append(getImports()); stringBuilder.append("\n"); stringBuilder.append(getHeader()); stringBuilder.append("\n"); stringBuilder.append(getTag()); stringBuilder.append("\n"); stringBuilder.append(FIELDS_PATTERN); stringBuilder.append("\n"); stringBuilder.append(getConstructor()); stringBuilder.append("\n"); stringBuilder.append(getGetView()); stringBuilder.append("\n"); stringBuilder.append(getHolder()); stringBuilder.append("}"); stringBuilder.append("\n"); return stringBuilder.toString(); } 


And finally, an example of work:

Activity

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ListView android:id="@+id/news_list_activity_news_list" android:layout_width="match_parent" android:layout_height="match_parent" /> <ImageView android:layout_width="fill_parent" android:layout_height="wrap_content" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" <ProgressBar android:id="@+id/articles_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/articles_exist_text_view" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:id="@+id/articles_exist_button" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </RelativeLayout> </RelativeLayout> 

Sheet item

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" android:paddingLeft="15dp" android:paddingRight="15dp" > <TextView android:id="@+id/row_news_date_header_text" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <RelativeLayout android:id="@+id/news_main_layout" android:layout_width="fill_parent" android:layout_height="wrap_content" > <FrameLayout android:id="@+id/row_news_image_container" android:layout_width="96dp" android:layout_height="68dp" > <ImageView android:id="@+id/row_news_icon" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" /> </FrameLayout> <TextView android:id="@+id/row_news_title_text" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/row_news_type_text" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <ImageView android:id="@+id/row_news_date_icon" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/row_news_date_text" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout> <View android:id="@+id/row_news_items_divider" android:layout_width="fill_parent" android:layout_height="1dp" android:background="@drawable/divider_gray_horizontal" /> </LinearLayout> 


Result

 package com.test.activity; import android.os.Bundle; import android.app.Activity; import android.widget.ProgressBar; import android.widget.TextView; import android.graphics.Typeface; import com.test.R; import android.widget.ListView; import android.widget.Button; import android.view.View.OnClickListener; import android.view.View; public class NewsListActivityActivity extends Activity { private static final String TAG = NewsListActivityActivity.class.getSimpleName(); private ProgressBar articlesProgress; private ListView newsListActivityNewsList; private TextView articlesExistTextView; private Button articlesExistButton; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.news_list_activity); initActionBar(); initViews(); setFonts(); setListeners(); } private void initActionBar(){ } private void initViews(){ articlesProgress = (ProgressBar) findViewById(R.id.articles_progress); newsListActivityNewsList = (ListView) findViewById(R.id.news_list_activity_news_list); articlesExistTextView = (TextView) findViewById(R.id.articles_exist_text_view); articlesExistButton = (Button) findViewById(R.id.articles_exist_button); } private void setFonts(){ Typeface roboto = null;//TODO init this by utils articlesExistTextView.setTypeface(roboto); } private void setListeners(){ articlesExistButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { } }); } } 

 package com.test.adapter; import com.test.R; import android.graphics.Typeface; import android.content.Context; import java.util.List; import android.widget.ArrayAdapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.graphics.Typeface; import android.widget.ImageView; import android.widget.FrameLayout; import android.widget.RelativeLayout; import android.view.View.OnClickListener; import android.view.View; public class NewsListActivityAdapter extends ArrayAdapter<String>{ private static final String TAG = NewsListActivityAdapter.class.getSimpleName(); private Context context; private LayoutInflater inflater; public NewsListActivityAdapter(Context context, List<String> objects) { super(context, R.layout.news_list_item, objects); inflater = LayoutInflater.from(context); this.context = context; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null){ convertView = inflater.inflate(R.layout.news_list_item, parent, false); holder = new ViewHolder(convertView); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } String item = getItem(position); if (item != null){ holder.populateForm(item); } return convertView; } private class ViewHolder{ private TextView rowNewsDateHeaderText; private TextView rowNewsDateText; private TextView rowNewsTypeText; private TextView rowNewsTitleText; public ViewHolder(View v){ initViews(v); setFonts(); } private void initViews(View v){ rowNewsDateHeaderText = (TextView) v.findViewById(R.id.row_news_date_header_text); rowNewsDateText = (TextView) v.findViewById(R.id.row_news_date_text); rowNewsTypeText = (TextView) v.findViewById(R.id.row_news_type_text); rowNewsTitleText = (TextView) v.findViewById(R.id.row_news_title_text); } private void setFonts(){ Typeface roboto = null;//TODO init this by utils rowNewsDateHeaderText.setTypeface(roboto); rowNewsDateText.setTypeface(roboto); rowNewsTypeText.setTypeface(roboto); rowNewsTitleText.setTypeface(roboto); } public void populateForm(String item) { } } } 


How to use



1. Copy the plug-in jar file into the Eclipse plugins folder.
2. Restart Eclipse.
3. Select the necessary xml files of the rendered screens.
4. Right-click on the selected files -> Android Code Generator.
5. Use the received files from the% packagename% .activity and% packagename% .adapter packages.

Conclusion



I hope that this will make life easier for someone. The plugin works on Eclipse 4.2. All source code can be found here .

Thanks for attention.

PS First I decided to make a plugin for Eclipse. In the future, I think to do the same for Android Studio.

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


All Articles