Fragments
An Activity may comprise one or more fragments, each of which represents a portion of a user interface. Multiple fragments can be combined in a single actiity to build a multi-pane screen. In this lab our purpose in introducing fragments is to later facilitat the inclusion of a horizontal swipe feature to the ResidenceActivity class.
Preview
In this lab we introduce fragments (Figure 1). Each activity is refactored and replaced by an activity-fragment pair.
ResidenceActivity
Before proceeding with refactoring ResidenceActivity, it is necessary to make some changes as follows:
MyRentApp
Declare a protected MyRentApp field, initialize it in onCreate and provide a getter: protected static MyRentApp app;
@Override
public void onCreate() {
...
app = this;
...
}
public static MyRentApp getApp(){
return app;
}
Residence (model)
Protect against a negative id: public Residence() {
id = unsignedLong();
...
}
/**
* Generate a long greater than zero
* @return Unsigned Long value greater than zero
*/
private Long unsignedLong() {
long rndVal = 0;
do {
rndVal = new Random().nextLong();
} while (rndVal <= 0);
return rndVal;
}
Continue building the MyRent app that you developed in the previous lab.
Much of the content of ResidenceActivity inherited from the previous version is being moved to its associated new fragment class, ResidenceFragment.
We now begin refactoring:
- Replace the contents of ResidenceActivity with the following:
package org.wit.myrent.activities;
import org.wit.myrent.R;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
public class ResidenceActivity extends AppCompatActivity
{
ActionBar actionBar;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_container);
actionBar = getSupportActionBar();
FragmentManager manager = getSupportFragmentManager();
Fragment fragment = manager.findFragmentById(R.id.fragmentContainer);
if (fragment == null)
{
fragment = new ResidenceFragment();
manager.beginTransaction().add(R.id.fragmentContainer, fragment).commit();
}
}
}
Add a container for the associated fragment:
Filename: res/layout/fragment_container.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
ResidenceFragment
Here is the new ResidenceFragment class which comprises, mostly, code migrated from ResidenceActivity in the previous version of MyRent:
package org.wit.myrent.activities;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import org.wit.android.helpers.ContactHelper;
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.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.app.DatePickerDialog;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.DatePicker;
import android.widget.EditText;
import static org.wit.android.helpers.ContactHelper.sendEmail;
import static org.wit.android.helpers.IntentHelper.navigateUp;
public class ResidenceFragment extends Fragment implements TextWatcher,
OnCheckedChangeListener,
OnClickListener,
DatePickerDialog.OnDateSetListener
{
public static final String EXTRA_RESIDENCE_ID = "myrent.RESIDENCE_ID";
private static final int REQUEST_CONTACT = 1;
private EditText geolocation;
private CheckBox rented;
private Button dateButton;
private Button tenantButton;
private Button reportButton;
private Residence residence;
private Portfolio portfolio;
private String emailAddress = "";
MyRentApp app;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
Long resId = (Long) getActivity().getIntent().getSerializableExtra(EXTRA_RESIDENCE_ID);
app = MyRentApp.getApp();
portfolio = app.portfolio;
residence = portfolio.getResidence(resId);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
{
super.onCreateView(inflater, parent, savedInstanceState);
View v = inflater.inflate(R.layout.fragment_residence, parent, false);
ResidenceActivity residenceActivity = (ResidenceActivity)getActivity();
residenceActivity.actionBar.setDisplayHomeAsUpEnabled(true);
addListeners(v);
updateControls(residence);
return v;
}
private void addListeners(View v)
{
geolocation = (EditText) v.findViewById(R.id.geolocation);
dateButton = (Button) v.findViewById(R.id.registration_date);
rented = (CheckBox) v.findViewById(R.id.isrented);
tenantButton = (Button) v.findViewById(R.id.tenant);
reportButton = (Button) v.findViewById(R.id.residence_reportButton);
geolocation .addTextChangedListener(this);
dateButton .setOnClickListener(this);
rented .setOnCheckedChangeListener(this);
tenantButton.setOnClickListener(this);
reportButton.setOnClickListener(this);
}
public void updateControls(Residence residence)
{
geolocation.setText(residence.geolocation);
rented.setChecked(residence.rented);
dateButton.setText(residence.getDateString());
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch (item.getItemId())
{
case android.R.id.home: navigateUp(getActivity());
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onPause()
{
super.onPause();
portfolio.saveResidences();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (resultCode != Activity.RESULT_OK)
{
return;
}
switch (requestCode)
{
case REQUEST_CONTACT:
String name = ContactHelper.getContact(getActivity(), data);
emailAddress = ContactHelper.getEmail(getActivity(), data);
tenantButton.setText(name + " : " + emailAddress);
residence.tenant = name;
break;
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after)
{ }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count)
{}
@Override
public void afterTextChanged(Editable c)
{
Log.i(this.getClass().getSimpleName(), "geolocation " + c.toString());
residence.geolocation = c.toString();
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
{
residence.rented = isChecked;
}
@Override
public void onClick(View v)
{
switch (v.getId())
{
case R.id.registration_date : Calendar c = Calendar.getInstance();
DatePickerDialog dpd = new DatePickerDialog (getActivity(), this, c.get(Calendar.YEAR), c.get(Calendar.MONTH), c.get(Calendar.DAY_OF_MONTH));
dpd.show();
break;
case R.id.tenant : Intent i = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
startActivityForResult(i, REQUEST_CONTACT);
if (residence.tenant != null)
{
tenantButton.setText("Tenant: "+residence.tenant);
}
break;
case R.id.residence_reportButton : sendEmail(getActivity(), "emailAddress", getString(R.string.residence_report_subject), residence.getResidenceReport(getActivity()));
break;
}
}
@Override
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth)
{
Date date = new GregorianCalendar(year, monthOfYear, dayOfMonth).getTime();
residence.date = date.getTime();
dateButton.setText(residence.getDateString());
}
}
Rename res/layout/activity_residence.xml to the more appropriate name: fragment_residence.xml.
ResidenceListActivity
As is the case with ResidenceActivity, much of ResidenceListActivity's code is also moved to its new associated fragment class - ResidenceListFragment.
Here is the refactored ResidenceListActivity:
package org.wit.myrent.activities;
import org.wit.myrent.R;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
public class ResidenceListActivity extends AppCompatActivity
{
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_container);
FragmentManager manager = getSupportFragmentManager();
Fragment fragment = manager.findFragmentById(R.id.fragmentContainer);
if (fragment == null)
{
fragment = new ResidenceListFragment();
manager.beginTransaction().add(R.id.fragmentContainer, fragment).commit();
}
}
}
ResidenceListFragment
Here is the ResidenceListFragment class which comprises, mostly, code migrated from ResidencyListActivity in the previous version of MyRent:
package org.wit.myrent.activities;
import java.util.ArrayList;
import org.wit.android.helpers.IntentHelper;
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.widget.ListView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.TextView;
import android.widget.CheckBox;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.widget.AdapterView.OnItemClickListener;
public class ResidenceListFragment extends ListFragment implements OnItemClickListener
{
private ArrayList<Residence> residences;
private Portfolio portfolio;
private ResidenceAdapter adapter;
MyRentApp app;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
getActivity().setTitle(R.string.app_name);
app = MyRentApp.getApp();
portfolio = app.portfolio;
residences = portfolio.residences;
adapter = new ResidenceAdapter(getActivity(), residences);
setListAdapter(adapter);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
View v = super.onCreateView(inflater, parent, savedInstanceState);
return v;
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Residence res = ((ResidenceAdapter) getListAdapter()).getItem(position);
Intent i = new Intent(getActivity(), ResidenceActivity.class);
i.putExtra(ResidenceFragment.EXTRA_RESIDENCE_ID, res.id);
startActivityForResult(i, 0);
}
@Override
public void onResume() {
super.onResume();
((ResidenceAdapter) getListAdapter()).notifyDataSetChanged();
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.residencelist, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_item_new_residence:
Residence residence = new Residence();
portfolio.addResidence(residence);
Intent i = new Intent(getActivity(), ResidenceActivity.class);
i.putExtra(ResidenceFragment.EXTRA_RESIDENCE_ID, residence.id);
startActivityForResult(i, 0);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Residence residence = adapter.getItem(position);
IntentHelper.startActivityWithData(getActivity(), ResidenceActivity.class, "RESIDENCE_ID", residence.id);
}
class ResidenceAdapter extends ArrayAdapter<Residence>
{
private Context context;
public ResidenceAdapter(Context context, ArrayList<Residence> residences) {
super(context, 0, residences);
this.context = context;
}
@SuppressLint("InflateParams")
@Override
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null) {
convertView = inflater.inflate(R.layout.list_item_residence, null);
}
Residence res = getItem(position);
TextView geolocation = (TextView) convertView.findViewById(R.id.residence_list_item_geolocation);
geolocation.setText(res.geolocation);
TextView dateTextView = (TextView) convertView.findViewById(R.id.residence_list_item_dateTextView);
dateTextView.setText(res.getDateString());
CheckBox rentedCheckBox = (CheckBox) convertView.findViewById(R.id.residence_list_item_isrented);
rentedCheckBox.setChecked(res.rented);
return convertView;
}
}
}
Build and install the app on a device and verify that it functions correctly. No observable difference should exist in the performance of tha application as a result of introducing fragments.
Test
Build and install the app on a phsical device or emulator:
- Create a new residence
- Explore the various features.
- No difference in the behaviour of the app will be obvious as a consequence of introducing fragments.
- The need for and use of fragments will be demonstrated later in the course.
The application at the end of this lab is available for reference here: myrent-07