Friday, September 28, 2012

Skeleton for Activity


Skeleton for Activity
During the development of games and applications are often situations when to have developed in each Activity to write the same code. Then transfer it to other projects. In this article I would like to talk about how you can make life easier when creating the next Activity in the application.


Theory

What we often use when creating a new Activity? Of course this setting displays a View (method setContentView()), sending statistics, using Wake Lock, logging methods, start and stop the music, and more. Why do none of these methods to transfer to another class, and then extend from it in our Activity. 

Practice

Develop an abstract class: SceletonActivity. Subclass android.app.Activity, so you can use the appropriate methods.
public abstract class SceletonActivity extends Activity {
}
In this article, I will add to our "skeleton" traceability applications pause using Wake Lock, logging and sending statistics.
Add the basic methods used in our Activity. I use for logging class, I described in the article "Logging in Android». As well add the attribute protected String tag. It will be used to store the current class name.
    protected String tag;  // the class name for logging
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LogSystem.w(tag, "onCreate()");
   }
    
    @Override
 protected void onPause() {
  super.onPause();
  LogSystem.w(tag, "onPause()");
 }
 
 @Override
 protected void onResume() {
  super.onResume();
  LogSystem.w(tag, "onResume()");
  this.mPaused = false;
  wlAsquire();
 } 
  
 @Override
 protected void onDestroy() {
  super.onDestroy();
  LogSystem.w(tag, "onDestroy()");
 }

 @Override
 protected void onStart() {
  super.onStart();
  LogSystem.w(tag, "onStart()");
 }

 @Override
 protected void onStop() {
  super.onStop();
  LogSystem.w(tag, "onStop()");
 }
The next stage in the development of our "skeleton" - tracking pauses in the application. To do this, we add an attribute protected boolean mPaused. And slightly modify our methods:
    protected boolean mPaused; // monitors the pause in the application
    
    @Override
 protected void onPause() {
  super.onPause();
  LogSystem.w(tag, "onPause()");
  this.mPaused = true;
 }
 
 @Override
 protected void onResume() {
  super.onResume();
  LogSystem.w(tag, "onResume()");
  this.mPaused = false;
 }
What you can do this attribute, I will show later in this article.
Now we need to install the appropriate View and Tag for Activity, and initialize all of the elements of the user interface of our Activity. For this we will use abstract methods:
protected abstract void initInterface(); // initialization interface
protected abstract void setAttr(); // set the attributes of the class
Their implementation founded in the Activity, which will be the descendants of SceletonActivity. Calling these methods must be placed in the method onCreate() of our "skeleton":
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);        
        setAttr(); 
        LogSystem.w(tag, "onCreate()");        
        setContentView(view);
        initInterface();
    }
We also want to send statistics to our use of services (eg, Google Analitics or Flurry). To do this, we add the following method:
public void trackSupportedApp(String activity,  Map map) {
  LogSystem.w(tag, "trackSupportedApp(" + activity + ")");
    }
Instead of logging, you must add code to send statistics for services you use. For example, for Flurry code may look like this:
public static void trackSupportedApp(String activity,  Map map) {
  if (activity == null || activity.length() == 0) return;
        if (map == null)
         FlurryAgent.logEvent(activity);
        else
         FlurryAgent.logEvent(activity, map);
    }
And lastly I will add WakeLock, that our application does not screen turns off while you work.
For this we need 3 methods:
 /**
  * Initialization WakeLock
  */
 private void initWl()
 {
  if (wl == null)
        {
         PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
   wl = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
     | PowerManager.ACQUIRE_CAUSES_WAKEUP, "org.snowpard.projects.five"); 
        }
 }
 
 /**
  * Runs the requested function WakeLock
  */
 public void wlAsquire()
 {
  try 
  {
   if (wl != null)
    wl.acquire();
  }catch (Exception e) {
  }
 }
 
 /**
  * Stop the execution of the requested function WakeLock
  */
 public void wlRelease()
 {
  try 
  {
   if (wl != null)
    wl.release();
  }catch (Exception e) {
  }
 } 
Again change our methods to use the new features:
        @Override
        public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
        
         setAttr(); 
         LogSystem.w(tag, "onCreate()");
        
         setContentView(view);
         trackSupportedApp(tag, null);
         initWl();
         initInterface();
        }
    
        @Override
 protected void onPause() {
  super.onPause();
  LogSystem.w(tag, "onPause()");
  this.mPaused = true;
  wlRelease();
 }
 
 @Override
 protected void onResume() {
  super.onResume();
  LogSystem.w(tag, "onResume()");
  this.mPaused = false;
  wlAsquire();
 }

Use SceletonActivity

Now I'll show how to simplify the design of our Activity. Develop an application that contains two Activity and buttons to move between them.
Example strings.xml
<resources> 
    <string name="btn_next">Next Activity</string>
    <string name="btn_previous">Previous Activity</string>
    <string name="app_name">Skeleton Example</string> 
</resources>
An example of the layout file for our Activity:
first.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" > 
    <Button
        android:id="@+id/btn_next"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/btn_next" /> 
</LinearLayout>

second.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" > 
    <Button
        android:id="@+id/btn_previous"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/btn_previous" /> 
</LinearLayout>
And classes:
FirstActivity.java
public class FirstActivity extends SceletonActivity {

 @Override
 protected void initInterface() {
  Button btn = (Button)findViewById(R.id.btn_next);
  btn.setOnClickListener(new OnClickListener() {
   
   @Override
   public void onClick(View v) {
    Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
    startActivity(intent);    
   }
  });
 }

 @Override
 protected void setAttr() {
  this.tag = FirstActivity.class.getSimpleName();  
  this.view = R.layout.first;
 }

}

SecondActivity.java
public class SecondActivity extends SceletonActivity {

 @Override
 protected void initInterface() {
  Button btn = (Button)findViewById(R.id.btn_previous);
  btn.setOnClickListener(new OnClickListener() {
   
   @Override
   public void onClick(View v) {
    finish();   
   }
  });
 }

 @Override
 protected void setAttr() {
  this.tag = SecondActivity.class.getSimpleName();  
  this.view = R.layout.second;
 }

}
As you can see, there is no duplicate code, the code itself is compact enough, and our Activity versatile.
Do not forget to add created Activity in AndroidManifest:
<activity
            android:name=".FirstActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".SecondActivity"
            android:label="@string/app_name" >
        </activity>

Attribute mPaused

As I promised, I will tell a little about the attribute mPaused, which is used to track a pause in the application. How can it be used? It happens that when you press the button, the effect is not immediate (the device are different), and user can perform second press, which, for example, it would be in your application is not desirable. For this and used mPaused. Example of use:
btn.setOnClickListener(new OnClickListener() {
 @Override
 public void onClick(View v) {
  if (!mPaused)
  {
   Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
   startActivity(intent);
   mPaused = true;
  }
 }
});

Links

The source codes of this project can be downloaded here: zip