Residence List Deletion & ViewPager

In this lab we introduce a facility to select and delete a subset of residences in the list view. The selected items need not be contiguous. Also, introduce a new feature to support 'swipe' interaction. This allows a user to swipe left/right to page through the residence list.

Preview

Figure 1: Long press to engage contextual menu to delete selection from list

Figure 2: Use ViewPager to introduce swipe facility

Resources (Contextual Menu)

Add a new context menu resource.

Filename: res/menu/residence_list_context.xml

<menu
  xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/menu_item_delete_residence"
    android:icon="@android:drawable/ic_menu_delete"
    android:title="@string/delete_residence" />
</menu>

We would like the selected residences to appear highlighted for ease of identification. Add the following xml file in a new folder drawable:

Filename: res/drawable/background_activated.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
  <item
    android:state_activated="true"
    android:drawable="@android:color/darker_gray"
    />
</selector>

Activate the background_activated state by adding the following attribute to res/layout/list_item_residence.xml.

    android:background="@drawable/background_activated"

Here is the complete file:

Filename: list_item_residence.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/background_activated"
    android:orientation="vertical">

    <CheckBox
        android:id="@+id/residence_list_item_isrented"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_alignParentRight="true"
        android:enabled="false"
        android:focusable="false"
        android:padding="4dp"
         />

    <TextView
        android:id="@+id/residence_list_item_geolocation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@id/residence_list_item_isrented"
        android:textStyle="bold"
        android:paddingLeft="4dp"
        android:paddingRight="4dp"
        />

    <TextView
        android:id="@+id/residence_list_item_dateTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/residence_list_item_geolocation"
        android:layout_toLeftOf="@id/residence_list_item_isrented"
        android:paddingLeft="4dp"
        android:paddingRight="4dp"
        android:paddingTop="4dp"/>

</RelativeLayout>

Add string resource:

<string name="delete_residence">Delete Residence</string>  

Press and hold trash can to display menu label

ResidenceListFragment

Add imports:

import android.view.ActionMode;
import android.widget.AbsListView.MultiChoiceModeListener;

Change class signature by implementing MultiChoiceModeListener:

public class ResidenceListFragment extends ListFragment implements OnItemClickListener, MultiChoiceModeListener

Add a ListView field:

private ListView listView;

In onCreateView initialize the listView and set listener:

    listView = (ListView) v.findViewById(android.R.id.list);
    listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
    listView.setMultiChoiceModeListener(this);

Implement MultiChoiceModeListener methods:

  /* ************ MultiChoiceModeListener methods (begin) *********** */
  @Override
  public boolean onCreateActionMode(ActionMode mode, Menu menu)
  {
    MenuInflater inflater = mode.getMenuInflater();
    inflater.inflate(R.menu.residence_list_context, menu);
    return true;
  }

  @Override
  public boolean onPrepareActionMode(ActionMode mode, Menu menu)
  {
    return false;
  }

  @Override
  public boolean onActionItemClicked(ActionMode mode, MenuItem item)
  {
    switch (item.getItemId())
    {
    case R.id.menu_item_delete_residence:
      deleteResidence(mode);
      return true;
    default:
      return false;
    }

  }

  private void deleteResidence(ActionMode mode)
  {
    for (int i = adapter.getCount() - 1; i >= 0; i--)
    {
      if (listView.isItemChecked(i))
      {
        portfolio.deleteResidence(adapter.getItem(i));
      }
    }
    mode.finish();
    adapter.notifyDataSetChanged();
  }

  @Override
  public void onDestroyActionMode(ActionMode mode)
  {
  }

  @Override
  public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked)
  {
  }

  /* ************ MultiChoiceModeListener methods (end) *********** */

Model Layer

Add a method to Portfolio to delete a residence :

  public void deleteResidence(Residence c)
  {
    residences.remove(c);
  }

ViewPager

Here we shall refactor MyRent by incorporating a ViewPager.

Figure 1: Swipe between pages

The refactoring is shown in Figure 2 where the changes are highlighted: compare this object diagram to that in the previous step and you will see that the main change is the replacement of ResidenceActivity with a new class ResidencePagerActivity.

Figure 2: Object diagram following introduction of ViewPager

Resources (ViewPager)

Create a new file ids.xml in res/values folder which will be used to store viewPager id numbers:

Filename: ids.xml

<resources>
    <item type="id" name="viewPager" />
</resources>

Change the ResidenceActivity node in the manifest file to ResidencePagerActivity.

    <activity 
        android:name=".activities.ResidencePagerActivity"
        android:label="@string/app_name">

        <meta-data  
          android:name="android.support.PARENT_ACTIVITY"
          android:value=".activities.ResidenceListActivity"/>
    </activity>

Create ResidencePagerActivity

Note: not all import statements are included:

Delete ResidenceActivity and replace with ResidencePagerActivity, the code for which follows:

Filename: ResidencePagerActivity.java

package org.wit.myrent.activities;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;


public class ResidencePagerActivity extends FragmentActivity
{
  private ViewPager viewPager;


  @Override
  public void onCreate(Bundle savedInstanceState) 
  {
    super.onCreate(savedInstanceState);
  }
}

This file is necessary because ViewPager requires a resource id.

Create and show ViewPager:

    viewPager = new ViewPager(this);
    viewPager.setId(R.id.viewPager);
    setContentView(viewPager);

Import:

import org.wit.myrent.R;

Next we introduce code to facilitate retrieval of views that are required as a user swipes left or right.

Introduce instance variables for a list of residences and portfolio:

  private ArrayList<Residence> residences;  
  private Portfolio portfolio;

Add this private method to obtain a reference to the list of references stored in the model layer:

  private void setResidenceList()
  {
    MyRentApp app = (MyRentApp) getApplication();
    portfolio = app.portfolio; 
    residences = portfolio.residences;    
  }

Invoke setResidenceList immediately following setContentView in onCreate.

Add this private class at the end of ResidencePagerActivity.

  class PagerAdapter extends FragmentStatePagerAdapter 
  {
    private ArrayList<Residence>  residences; 

    public PagerAdapter(FragmentManager fm, ArrayList<Residence> residences)
    {
      super(fm);
      this.residences = residences;
    }

    @Override
    public int getCount()  
    {  
      return residences.size();  
    }

    @Override
    public Fragment getItem(int pos) 
    {
      Residence residence = residences.get(pos);
      Bundle args = new Bundle();
      args.putSerializable(ResidenceFragment.EXTRA_RESIDENCE_ID, residence.id);
      ResidenceFragment fragment = new ResidenceFragment();
      fragment.setArguments(args);
      return fragment;
    } 
  }

Ensure this new class is contained within ResidencePagerActivity as shown here:

Change the signature of ResidencePagerActivity to the following:

public class ResidencePagerActivity extends FragmentActivity implements ViewPager.OnPageChangeListener

Official documentation on ViewPager.OnPageChangeListener is available here.

This change will trigger errors that may be eliminated by using QuickFix to implement the required interface methods:


  @Override
  public void onPageScrollStateChanged(int arg0)
  {
  }

  @Override
  public void onPageScrolled(int arg0, float arg1, int arg2)
  {
  }

  @Override
  public void onPageSelected(int arg0)
  {
  }

We are only interested in full implementation of onPageScrolled. Here it is:


  @Override
  public void onPageScrolled(int arg0, float arg1, int arg2)
  {
    info(this, "onPageScrolled: arg0 "+arg0+" arg1 "+arg1+" arg2 "+arg2);
    Residence residence = residences.get(arg0);
    if (residence.geolocation != null)
    {
      setTitle(residence.geolocation);
    }
  }

The method info is implemented in the helpers folder and may be imported in a similar manner as previously in, for example, ResidenceFragment.

This last feature is illustrated in Figure 1 below.

Figure 1: Changing Action Bar Title

Next add a PageAdapter instance variable:

  private PagerAdapter          pagerAdapter;
    pagerAdapter = new PagerAdapter(getSupportFragmentManager(), residences);
    viewPager.setAdapter(pagerAdapter);
    viewPager.setOnPageChangeListener(this);

Integrate ResidencePagerActivity

To date in ResidenceListActivity.onListItemClick:

We now wish to start ResidencePagerActivity:

with this:

Intent i = new Intent(getActivity(), ResidencePagerActivity.class);

In ResidenceListFragment, replace any other references to ResidenceActivity with ResidencePagerActivity.

Run MyRent and click on any element in the list: this should open the associated details view.

Observe, however, that irrespective which residence on the list is pressed, the first on the list is displayed in the details view. This will now be fixed by implementing a method setCurrentItem in ResidencePagerActivity:

  /*
   * Ensure selected residence is shown in details view
   */
  private void setCurrentItem()
  {
    UUID res = (UUID) getIntent().getSerializableExtra(ResidenceFragment.EXTRA_RESIDENCE_ID);
    for (int i = 0; i < residences.size(); i++)
    {
      if (residences.get(i).id.toString().equals(res.toString()))
      {
        viewPager.setCurrentItem(i);
        break;
      }
    }
  }

An import statement is necessary for the UUID:

import java.util.UUID;

Invoke setCurrentItem at the end of ResidencePagerActivity.onCreate.

Here is a brief description of setCurrentItem:

Here is the final onCreate:

  @Override
  public void onCreate(Bundle savedInstanceState) 
  {
    super.onCreate(savedInstanceState);
    viewPager = new ViewPager(this);
    viewPager.setId(R.id.viewPager);
    setContentView(viewPager);
    setResidenceList();
    pagerAdapter = new PagerAdapter(getSupportFragmentManager(), residences);
    viewPager.setAdapter(pagerAdapter); 
    viewPager.setOnPageChangeListener(this);
    setCurrentItem();
  }

For reference, the complete list of imports is provided below:

import java.util.ArrayList;
import java.util.UUID;

import org.wit.myrent.R;
import org.wit.myrent.app.MyRentApp;
import org.wit.myrent.models.Portfolio;
import org.wit.myrent.models.Residence;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager;
import static org.wit.android.helpers.LogHelpers.info;

ResidenceFragment

One final item needs attention. Replace this line in ResidenceFragment:

with this:

UUID resId = (UUID)getArguments().getSerializable(EXTRA_RESIDENCE_ID);

This last line of code extracts the current residence id from the Bundle object that was created in the PagerAdapter.getItem method which is located in the file ResidencePagerActivity.

Build and lauch the app on an emulator or device and check the functionality we have introduced in this lab.

Archives

This is a version of MyRent complete to the end if this lab: