Introduction
I am going to reuse the sample code developed by “Martin Knudsen”. The entire credit for developing the application goes to Martin Knudsen.
This project is about Today’s menu, the author developed this one as a learning material for students.
The sample project utilizes some of the most commonly used Android Wear components. So it’s always good to have some understanding about android widgets such as BoxInsetLayout, WearableListView, FrameLayout, LinearLayout etc.
TodayMenu shows screen in four fragments, the main fragment shows the list of choices which is nothing but food items. The second one shows the statistics of the selected food items. The third one accepts or takes in a voice input nothing but a food item name and temporarily saves in SQLite database. The last fragment shows two buttons, one to reset the statistics and the other to rest food choices.
Please take a look into the following link to have some understanding about Android Wear
TodayMenu Application UI
Before we dig into the TodayMenu application functionality, let us take a quick look into the application UI screens.

Database custom class
We have a class name Database which extends itself from SQLiteOpenHelper. There are two methods that we are overriding i.e onCreate and onUpdate. The following is the code snippet of onCreateoverride. We are executing SQL script to create tables for menu and choices.
- @Override
- public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE menu ( id INTEGER PRIMARY KEY AUTOINCREMENT," +
- "name TEXT, weekday INTEGER);");
- db.execSQL("CREATE TABLE choices ( id INTEGER PRIMARY KEY AUTOINCREMENT," +
- "name TEXT);");
- }
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- if (oldVersion==1 && newVersion==2)
- db.execSQL("CREATE TABLE choices ( id INTEGER PRIMARY KEY AUTOINCREMENT," +
- "name TEXT);");
- }
MainActivity XML and Code
Now let us take a look into the main activity xml and the associated code. Below is the code snippet of the activity.xml. You can notice a BoxInsetLayout is being used so the same UI can be displayed on rounded or square watches. It composes GridViewPager and DotsPageIndicator.
- <android.support.wearable.view.BoxInsetLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="match_parent"
- android:layout_width="match_parent">
- <!-- This is the gridviewpager, it makes sure we can swipe between different views -->
- <android.support.wearable.view.GridViewPager
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/pager"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
- <!-- This is the DotsPageIndicator, it makes sure we can use the small
- dots on the bottom of the screen to indicate the current page of the app is displayed -->
- <android.support.wearable.view.DotsPageIndicator
- android:id="@+id/page_indicator"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal|bottom">
- </android.support.wearable.view.DotsPageIndicator>
- </android.support.wearable.view.BoxInsetLayout>
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity);
- final Resources res = getResources();
- final GridViewPager pager = (GridViewPager) findViewById(R.id.pager);
- pager.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener() {
- @Override
- public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
- final boolean round = insets.isRound();
- int rowMargin = res.getDimensionPixelOffset(R.dimen.page_row_margin);
- int colMargin = res.getDimensionPixelOffset(round ?
- R.dimen.page_column_margin_round : R.dimen.page_column_margin);
- pager.setPageMargins(rowMargin, colMargin);
- pager.onApplyWindowInsets(insets);
- return insets;
- }
- });
- pager.setAdapter(new SampleGridPagerAdapter(this, getFragmentManager()));
- pagerGlobal = pager;
- DotsPageIndicator dotsPageIndicator = (DotsPageIndicator) findViewById(R.id.page_indicator);
- dotsPageIndicator.setPager(pager);
- Database db = new Database(this);
- db.readChoices(); //when the app starts we need to read the choices from the database file
- }
- public String[] readChoices() {
- SQLiteDatabase database = getReadableDatabase();
- Cursor cursor = database.rawQuery("SELECT name FROM choices ORDER BY id",null);
- int count = cursor.getCount();
- if (count==0) //there is nothing, so first time we start the app.
- {
- for (String choice : Choices.ELEMENTS_RESET)
- database.execSQL("INSERT INTO choices (name) VALUES ('"+choice+"')");
- Choices.ELEMENTS = new String[Choices.ELEMENTS_RESET.length];
- System.arraycopy(Choices.ELEMENTS_RESET, 0, Choices.ELEMENTS, 0,
- Choices.ELEMENTS_RESET.length);
- cursor.close();
- return Choices.ELEMENTS_RESET; //we just have default values.
- }
- else
- {
- String[] elements = new String[count];
- int index = 0;
- while (cursor.moveToNext())
- {
- elements[index] = cursor.getString(0);
- index++;
- }
- Choices.ELEMENTS = elements; //overwrite the elements array
- cursor.close();
- return elements;
- }
- }
- public class Choices {
- public static String[] ELEMENTS_RESET = { "Chicken", "Beef", "Pork", "Lamb","Duck","Turkey" };
- public static String[] ELEMENTS;
- }
GridViewPager Adaper
The following is the code snippet of GridViewPager Adapter.
- public class SampleGridPagerAdapter extends FragmentGridPagerAdapter {
- MenuFragment menuFragment;
- ClearFragment clearFragment;
- StatsFragment statsFragment;
- SpeechFragment speechFragment;
- public SpeechFragment getSpeechFragment()
- {
- return speechFragment;
- }
- public ClearFragment getClearFragment()
- {
- return clearFragment;
- }
- public StatsFragment getStatsFragment()
- {
- return statsFragment;
- }
- public MenuFragment getMenuFragment()
- {
- return menuFragment;
- }
- public SampleGridPagerAdapter(Context ctx, FragmentManager fm) {
- super(fm);
- menuFragment = new MenuFragment();
- clearFragment = new ClearFragment();
- statsFragment = new StatsFragment();
- statsFragment.setContext(ctx); //we need the context in this class
- speechFragment = new SpeechFragment();
- }
- public void notifyStatsSetChanged() {
- statsFragment.updateUI();
- }
- public void listViewDataSetChanged() {
- menuFragment.resetList();
- }
- @Override
- public Fragment getFragment(int row, int col) {
- if (col==0)
- return menuFragment;
- else if (col==1)
- return statsFragment;
- else if (col==2)
- return speechFragment;
- else
- return clearFragment;
- }
- @Override
- public int getRowCount() {
- return 1; //we just have 1 row of pages, meaning scrolling horizontally
- }
- @Override
- public int getColumnCount(int rowNum) {
- return 4; //we just have 4 columns - fixed in this app.
- }
- }
Menu Fragment
Here's the code snippet of Menu fragment.
- public class MenuFragment extends Fragment implements WearableListView.ClickListener {
- WearableListView listView;
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.select, container, false);
- listView =(WearableListView) view.findViewById(R.id.wearable_list);
- if (listView!=null)
- {
- listView.setAdapter(new Adapter(getActivity().getApplicationContext(), Choices.ELEMENTS));
- // Set a click listener - using this activity
- listView.setClickListener(this);
- listView.setGreedyTouchMode(true);
- }
- return view;
- }
- ….
- }

Here’s the code snippet where we are handing the wearable listview onClick event.
- Firstly, we need to get the index of the selected listview item, we are obtaining the same from a “Tag” object coming next, you will see details on how we set the tag.
- Get the food choice based on the tag value.
- Create an instance of Database class and make a call to addFood so we can save our choice.
- An Intent instance is created to show a success confirmation to the user.
- In the end, we are going to update the stats fragment UI so when the user navigates, she/he can see the updated statistics of the choices the user has selected.
- @Override
- public void onClick(WearableListView.ViewHolder v) {
- Integer tag = (Integer) v.itemView.getTag();
- int index = tag.intValue();
- String chosen = Choices.ELEMENTS[index];
- Database db = new Database(getActivity());
- db.addFood(chosen);
- Intent intent = new Intent(getActivity().getApplicationContext(), ConfirmationActivity.class);
- intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE,
- ConfirmationActivity.SUCCESS_ANIMATION);
- intent.putExtra(ConfirmationActivity.EXTRA_MESSAGE,getResources()
- .getString(R.string.saved)+" "+chosen);
- startActivity(intent);
- ((SampleGridPagerAdapter) MainActivity.getPager().getAdapter()).notifyStatsSetChanged();
- }
The following is the code snippet of Menu fragment list view adapter. The adapter takes two parameters, one is the context and the other is a dataset instance. The dataset has all the list of choices to be displayed. There are two main overrides that we need to take care of i.e onCreateViewHolder and onBindViewHolder.
Within the onCreateViewHolder method, all we have to do is return an instance of WearableListView.ViewHolder.
Create a new instance of ItemViewHolder with the view. A LayoutInflator instance is used to inflate the layout R.layout.list_item. The onBindViewHolder method internally gets called where we get the ViewHolder instance and then get the TextView so that we can set appropriate text from the dataset by position. Also you can notice we are making a call to set the tag object with the position value so we can use the same in onClick event. This is done so we can get the right choice and save in database.
- private static final class Adapter extends WearableListView.Adapter {
- private String[] mDataset;
- private final Context mContext;
- private final LayoutInflater mInflater;
- public Adapter(Context context, String[] dataset) {
- mContext = context;
- mInflater = LayoutInflater.from(context);
- mDataset = dataset;
- }
- public static class ItemViewHolder extends WearableListView.ViewHolder {
- private TextView textView;
- public ItemViewHolder(View itemView) {
- super(itemView);
- textView = (TextView) itemView.findViewById(R.id.name);
- }
- }
- @Override
- public WearableListView.ViewHolder onCreateViewHolder(ViewGroup parent,
- int viewType) {
- return new ItemViewHolder(mInflater.inflate(R.layout.list_item, null));
- }
- @Override
- public void onBindViewHolder(WearableListView.ViewHolder holder,
- int position) {
- ItemViewHolder itemHolder = (ItemViewHolder) holder;
- TextView view = itemHolder.textView;
- view.setText(mDataset[position]);
- holder.itemView.setTag(position);
- }
- @Override
- public int getItemCount() {
- return mDataset.length;
- }
- }
Now let us see how to reset the list of choices. The following is the code snippet for the same. First we need to get the choice length and copy all the choices temporarily into a string array so we can reset the Choices.ELEMENTS and the ListView component by setting the adapter and refreshing the same by making a call to invalidate method.
- public void resetList() {
- int len = Choices.ELEMENTS_RESET.length;
- String[] newElements = new String[len];
- System.arraycopy(Choices.ELEMENTS_RESET, 0, newElements, 0, len);
- Choices.ELEMENTS = newElements;
- listView.setAdapter(new Adapter(getActivity().getApplicationContext(), Choices.ELEMENTS));
- listView.invalidate();
- }
Now let us take a look into the StatsFragment, where it shows the detailed statistics about the food choices the user chooses. The following is the partial code snippet of StatsFragment. We are making use of a LinearLayout where we have one TextView with a text set to “Statistics”. Coming next you will see how we are adding one more TextView component to LinearLayout so that we can show the real statistics to user.
- <LinearLayout
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:layout_gravity="center"
- android:orientation="vertical"
- android:id="@+id/statslayout">
- <TextView
- android:layout_height="wrap_content"
- android:layout_width="wrap_content"
- android:gravity="center_horizontal"
- android:textSize="24sp"
- android:layout_gravity="center_horizontal"
- android:textColor="@color/blue"
- android:text="@string/statistics"/>
- </LinearLayout>

Updating Statistics UI
It’s time to see how the statistics information is being shown to the user. Within the StatsFragment onCreate override, we are making a call to update the UI. The following is the code snippet for the same. Here’s what we do.
- Create an instance of Database and get the latest statistics from SQLite DB. Hold the same in ArrayList of Item type.
- Get the child count for the LinearLayout instance so that we can remove and add a view so the user can see the up to date refreshed view.
- The next few set of lines, we are looping through all the statistics; create a TextView instance and then set text, color, font, etc. and add the same to LinearLayout instance.
- public void updateUI() {
- Database db = new Database(context);
- ArrayList<Item> items = db.getStats();
- Collections.sort(items); //items are now sorted according to the frequency
- int children = parent.getChildCount();
- if (children>1) //do we have more than the "statistics" label, then remove them
- {
- parent.removeViews(1, children-1);
- }
- for (Item item : items) {
- TextView text = new TextView(getActivity());
- String p = String.format("%.1f", item.getPercent());
- text.setText(item.getName() + " : "+item.getFreq()+ " ("+p+" %)");
- text.setTextColor(Color.WHITE);
- text.setTextSize(22);
- text.setLayoutParams(new LayoutParams(
- LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT));
- parent.addView(text); //add the textview to the layout.
- }
- }
Let us take a look into the speech fragment and see the inner workings. The speech being highly important part of Android wear, the main functionality of this fragment being, accept new speech input from user and save the same as choices. The Speech fragment implements onClickListener, so it can handle user click. Here’s the code snippet for onCreateView override.
Within the onCreateView, first we get the view instance by inflating the speech layout. So we can find speech and add item buttons and attach onClick events for the same. Also do not forget to reset the textinput.
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.speech, container, false);
- Button button = (Button) view.findViewById(R.id.speechButton);
- button.setOnClickListener(this);
- button = (Button) view.findViewById(R.id.addItemButton);
- button.setOnClickListener(this);
- textView = (TextView) view.findViewById(R.id.speechText);
- textInput = "";
- return view;
- }

Handling the Speech Fragment onClick event
Let’s see how to handle the onClick event for speech and add item buttons. Here’s the code snippet where a call to displaySpeechRecognizer method is made to start the speech recognizer to accept the voice input.
- @Override
- public void onClick(View v) {
- if (v.getId()==R.id.speechButton) {
- displaySpeechRecognizer();
- }
- else if (v.getId()==R.id.addItemButton) {
- if (textInput.length()>0)
- addData();
- else {
- Toast toast = Toast.makeText(getActivity().getApplicationContext(),
- "No input to add",Toast.LENGTH_LONG);
- toast.show();;
- }
- }
- }
- private void displaySpeechRecognizer() {
- Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
- intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
- RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
- startActivityForResult(intent, SPEECH_CODE);
- }
Please note - Adding a new choice can be done within the speech fragment itself. But we should not forget to refresh the MenuFragment ListView UI.
- public void addData() {
- Intent intent = new Intent(getActivity(), ConfirmationActivity.class);
- intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE,
- ConfirmationActivity.SUCCESS_ANIMATION);
- intent.putExtra(ConfirmationActivity.EXTRA_MESSAGE,getResources()
- .getString(R.string.choiceAdded));
- startActivity(intent);
- MenuFragment frag = ((SampleGridPagerAdapter) MainActivity.getPager()
- .getAdapter()).getMenuFragment();
- frag.addData(textInput);
- }
The Clear Fragment is the final or last fragment that gets displayed on the GridViewPager. It extends from a Fragment class and implements OnClickListener. The following is the code snippet ofonCreateView method override, where you can see we are setting the onClickListerner to handle the user click.
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.clear, container, false);
- Button button = (Button) view.findViewById(R.id.clearDataButton);
- button.setOnClickListener(this);
- button = (Button) view.findViewById(R.id.clearChoicesButton);
- button.setOnClickListener(this);
- return view;
- }

It’s time to take a look into the onClick override and try to understand how we are actually handling the Clear Data and Clear Choices button click. On each of the button clicks, we are showing a custom dialog so the user can take suitable action.
MyDialogFragment is a custom class extends itself from DiaglogFragment and overrides onCreateDialog. It has two methods named positiveClick and negativeClick with no implementation and allows one to override. Below you can see how we handle the positiveClick method to clear data by making a call to clearData method.
- @Override
- public void onClick(View v) {
- if (v.getId()==R.id.clearDataButton) {
- MyDialogFragment dialog = new MyDialogFragment() {
- @Override
- protected void positiveClick() {
- super.positiveClick();
- clearData();
- }
- @Override
- protected void negativeClick() {
- super.negativeClick();
- }
- };
- Bundle bundle = new Bundle();
- bundle.putString("title",getResources().getString(R.string.deleteStatsTitle));
- bundle.putString("message",getResources().getString(R.string.deleteStatsMessage));
- dialog.setArguments(bundle);
- dialog.show(this.getFragmentManager(),"test"); //test is just a tag - not shown to the user
- }
- else if (v.getId()==R.id.clearChoicesButton) {
- DialogFragment newFragment = MyWearDialog.newInstance();
- newFragment.show(getFragmentManager(), "dialog");
- }
- }
Finally there is one important thing we need to do, that is – Notify the SampleGridPagerAdapter by making a call to notifyStatsSetChanged method on its adapter.
- public void clearData() {
- Database db = new Database(getActivity());
- db.clearData();
- db.close();
- Intent intent = new Intent(getActivity(), ConfirmationActivity.class);
- intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE,
- ConfirmationActivity.SUCCESS_ANIMATION);
- intent.putExtra(ConfirmationActivity.EXTRA_MESSAGE,
- getResources().getString(R.string.statsDeleted));
- startActivity(intent);
- ((SampleGridPagerAdapter) MainActivity.getPager().getAdapter()).notifyStatsSetChanged();
- }
Please note – After clearing choices, one should never forget to refresh the GridViewPager by notifying the same.
- @Override
- public void onClick(View v) {
- if (v.getId()==R.id.cancel_btn) {
- dismiss(); //just do nothing
- }
- else if (v.getId()==R.id.ok_btn) {
- clearChoices();//do something
- dismiss(); //then dismiss
- }
- }
- public void clearChoices() {
- Database db = new Database(getActivity());
- db.clearChoices();
- db.close();
- ((SampleGridPagerAdapter)MainActivity.getPager().getAdapter()).listViewDataSetChanged();
- Intent intent = new Intent(getActivity(), ConfirmationActivity.class);
- intent.putExtra(ConfirmationActivity.EXTRA_ANIMATION_TYPE,
- ConfirmationActivity.SUCCESS_ANIMATION);
- intent.putExtra(ConfirmationActivity.EXTRA_MESSAGE,getResources()
- .getString(R.string.choicesDeleted));
- startActivity(intent);
- }
Please take a look into the following article to know more about how to debug apps on wearables.
References
This article uses code sample developed by "Maritn Knudsen". Feel free to take a look into the following Github link. All attribution to the author is made starting from the beginning of the article.

Sr KarthigaPosted Feb 14, 2016, 7:53 AM
Good one sir
Nilesh JadavPosted Oct 19, 2015, 1:16 AM
Good Share !!
Ankit BansalPosted Oct 19, 2015, 12:43 AM
nice one..thanks for sharing with us..
Santhakumar MunuswamyPosted Oct 18, 2015, 2:14 AM
Good Article