Android Chronometer Timer

Hi, in this tutorial I will show you and I will try to explain how to introduce timer in your app, how to pause it and how to resume it.

First you need to know that we will have to use the Chronometer class which is after all a TextView. Yes, that’s true, Chronometer class is a direct subclass of TextView.  So, long story short, this is the class which will help us to add a timer to our app. Let’s start coding :D

1.  Create a new project and name your activity MainActivity (it should already be named this way when the project is created);

2. Now, go to res – layout  and you will have to see an xml called activity_main.xml. If you don’t use Android Studio you might see a different name and for the purpose of this tutorial you should rename it to “activity_main.xml”. Here you should add the following code:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <Button android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/button_start"
            android:text="Start"/>

    <Button android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/button_resume"
        android:text="Resume"/>
</LinearLayout>

We created a view with 2 buttons: Start and Resume.

3.  Now, before creating the second activity we have to do some additional steps.  So, create a new Java class and name it ApplicationContextProvider and follow the steps from this tutorial.

4.  Now create a new Java class and name it SharedPreferenceManager. This class will be used to persist the elapsed time. Add the following code:

package com.example.chronometer;

import android.content.Context;
import android.content.SharedPreferences;

/**
 * Manager (singleton) for SharedPreference operations
 */
public class SharedPreferenceManager {
    public static final String TIME_SPENT_ON_LEVEL = "time.spent.on.level";
    private static SharedPreferenceManager sInstance;

    /**
     * Used to make commits to the shared preferences file, don't use this directly, use the getter!!
     */
    private SharedPreferences mPreferencesManager;

    /**
     * Private Constructor
     */
    private SharedPreferenceManager() {

    }

    /**
     * Gets the instance(object) for this class
     *
     * @return The Instance of SharedManager singleton class
     */
    public static SharedPreferenceManager instance() {
        if (sInstance == null) {
            sInstance = new SharedPreferenceManager();
        }

        return sInstance;
    }

    public SharedPreferences getPreferencesManager() {

        if (mPreferencesManager == null) {
            mPreferencesManager = ApplicationContextProvider.getContext().getSharedPreferences("saveState", Context.MODE_PRIVATE);
        }

        return mPreferencesManager;
    }

    /**
     * Save the time spent on a level
     *
     * @param milliseconds time spent on a level
     */
    public void persistTimeSpentOnLevel(long milliseconds) {

        getPreferencesManager().edit().putLong(TIME_SPENT_ON_LEVEL, milliseconds).commit();

    }
    /**
     * Returns the time spent on a level or 0 if the game is new
     *
     * @return milliseconds passed since the game started
     */
    public long getTimeSpentOnLevel() {

        return getPreferencesManager().getLong(TIME_SPENT_ON_LEVEL, 0);

    }
}

5. Now we have to create the 2nd activity. Name it TimerActivity and add the following code:

package com.example.chronometer;

import android.app.Activity;
import android.os.Bundle;
import android.os.SystemClock;
import android.widget.Chronometer;


public class TimerActivity extends Activity {

    private Chronometer mChronometer;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_timer);

        mChronometer = (Chronometer)findViewById(R.id.chronometer);
    }

    @Override
    protected void onResume() {
        super.onResume();

        mChronometer.setBase(SystemClock.elapsedRealtime() + SharedPreferenceManager.instance().getTimeSpentOnLevel());
        mChronometer.start();
    }

    @Override
    protected void onPause() {
        super.onPause();

        long timeWhenStopped = mChronometer.getBase() - SystemClock.elapsedRealtime();
        SharedPreferenceManager.instance().persistTimeSpentOnLevel(timeWhenStopped);
        mChronometer.stop();
    }
}

6. Go to res – layout and name your newly created activity_timer.xml and add the following code:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="com.example.chronometer.TimerActivity"
    android:orientation="vertical">

    <Chronometer
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/chronometer"/>

</LinearLayout>

7. And the last step is to add the following code to the MainActivity class:

package com.example.chronometer;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button startButton = (Button)findViewById(R.id.button_start);
        startButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // reset the timer
                SharedPreferenceManager.instance().persistTimeSpentOnLevel(0);

                // start the Timer activity
                Intent intent = new Intent(MainActivity.this, TimerActivity.class);
                startActivity(intent);
            }
        });

        Button resumeButton = (Button)findViewById(R.id.button_resume);
        resumeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // start the Timer activity
                Intent intent = new Intent(MainActivity.this, TimerActivity.class);
                startActivity(intent);
            }
        });
    }
}

Maybe you wonder what is SystemClock.elapsedRealtime(); Well, this returns the time in milliseconds since your device booted included sleep time. This means that if you test your app on an emulator and the emulator opened 30 minutes ago, this method will return 1800 000 milliseconds (=30 min).

Also setBase() has this definition: set the time that the count-up timer is in reference to.

If we look inside the Chronometer class, the setBase() method uses SystemClock.elapsedRealtime(); when the text must be updated with the correct time and at some point they subtract this time from a variable that was initialized with SystemClock.elapsedRealtime(); So this means that if you want to start your count from 00:00 you have to set the base like this:

mChronometer.setBase(SystemClock.elapsedRealtime());

And I think that’s it. The final result is:

android chronometer main screenandroid chronometer timer screen

Get Android OS Version

If you ever need to get the version of the OS on which your app runs, you can try this out:

android.os.Build.VERSION.SDK_INT

And you can use it against(for more check this out):

android.os.Build.VERSION_CODES.[BASE-KITKAT]

 

Finally you end up with something like:

if(android.os.Build.VERSION.SDK_INT == android.os.Build.VERSION_CODES.HONEYCOMB){
// do whatever you want here
}

ListView Empty Message

When I first needed to set a message on a ListView that was empty, I tried different methods, but none of them worked, except the one that I will describe in this tutorial. So let’s start.

When you want to show a message to the user when the ListView is empty, you have to keep in mind the following 3 steps:

  • In the xml where the ListView is declared, create a TextView (the TextView can be inside a LinearLayout if you want) right below the ListView
  • Set the TextView’s id as “emptyElement”
  • And inside the activity, set the setEmptyView() property to the ListView

1.  Create an xml which will hold the ListView and name it “my_activity” and an activity called “MyActivity”.

2. Now, in the just created xml “my_activity”, you will have to set the ListView.  And right below the ListView, you will have to add a TextView. This will be used to display the empty message.

Important: The TextView must have as id the following name: “emptyElement”. This name is mandatory. The message won’t be displayed if you use another name.

This is how “my_activity” xml should look like:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MyActivity">

    <ListView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/listView"/>

    <TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/emptyElement"
        android:text="The list is empty"
        android:textStyle="bold"
        android:textSize="15sp"
        android:visibility="gone"
        android:layout_centerInParent="true"
          android:textColor="@android:color/darker_gray"/>

</RelativeLayout>

3. Create an xml for displaying items (when the list is not empty), and name it “list_item”.

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:id="@+id/list_item"
    android:gravity="center_vertical">


    <TextView android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/list_item_text_view"
        android:textSize="20sp"
        android:padding="10dp"
        android:layout_marginLeft="5dp"/>

</LinearLayout>

4. Create a new Java class for the custom adapter which will be used by the ListView and name “MyCustomAdapter”. The code for the adapter is written below:

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.ArrayList;


public class MyCustomAdapter extends BaseAdapter {
    private ArrayList<String> mListItems;
    private LayoutInflater mLayoutInflater;

    public MyCustomAdapter(Context context, ArrayList<String> arrayList){

        mListItems = arrayList;

        //get the layout inflater
        mLayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    @Override
    public int getCount() {
        //getCount() represents how many items are in the list
        return mListItems.size();
    }

    @Override
    //get the data of an item from a specific position
    //i represents the position of the item in the list
    public Object getItem(int i) {
        return null;
    }

    @Override
    //get the position id of the item from the list
    public long getItemId(int i) {
        return 0;
    }

    @Override

    public View getView(int position, View view, ViewGroup viewGroup) {

        // create a ViewHolder reference
        ViewHolder holder;

        //check to see if the reused view is null or not, if is not null then reuse it
        if (view == null) {
            holder = new ViewHolder();

            view = mLayoutInflater.inflate(R.layout.list_item, null);
            holder.itemName = (TextView) view.findViewById(R.id.list_item_text_view);

            // the setTag is used to store the data within this view
            view.setTag(holder);
        } else {
            // the getTag returns the viewHolder object set as a tag to the view
            holder = (ViewHolder)view.getTag();
        }

        //get the string item from the position "position" from array list to put it on the TextView
        String stringItem = mListItems.get(position);
        if (stringItem != null) {
            if (holder.itemName != null) {
                //set the item name on the TextView
                holder.itemName.setText(stringItem);
            }
        }

        //this method must return the view corresponding to the data at the specified position.
        return view;

    }

    /**
     * Static class used to avoid the calling of "findViewById" every time the getView() method is called,
     * because this can impact to your application performance when your list is too big. The class is static so it
     * cache all the things inside once it's created.
     */
    private static class ViewHolder {

        protected TextView itemName;

    }
}

5. Now go to MyActivity class and add the code below:

import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;


public class MyActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_activity);

        ListView listView = (ListView) findViewById(R.id.listView);

        // Create an empty array list of strings
        List<String> items = new ArrayList<String>();

        // Set the adapter
        MyCustomAdapter adapter = new MyCustomAdapter(items);
        listView.setAdapter(adapter);

        // Set the emptyView to the ListView
        listView.setEmptyView(findViewById(R.id.emptyElement));
    }
}

 

And this is it. Now, if you run the app you should see something like this:

ListView empty message

And if you populate the ArrayList of Strings, the message will disappear and the list items will be displayed.  (You can use a for statement in MyActivity where the empty array was created in order to populate the array of strings)

for (int i = 0; i < 5; i++) {
            items.add("Item " + i);
        }

populated listView