Modern Android Activities tend to be composed of one or more Fragments - as opposed to consisting of a single layouts. This introduces a slightly more complicated creation process. However, using fragments opens up other opportunities we will explore in later labs.
This lab comprises the following:
Refactoring MyRent: introduction of fragments (Figure 1)
Launching a map application from within MyRent
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:
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.FragmentActivity;
import android.support.v4.app.FragmentManager;
public class ResidenceActivity extends FragmentActivity
{
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 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"
/>
Here is the new ResidenceFragment class which comprises, mostly, code migrated from ResidencyActivity in the previous version of MyRent:
package org.wit.myrent.activities;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.UUID;
import org.wit.android.helpers.ContactHelper;
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.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.IntentHelper.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;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
UUID resId = (UUID)getActivity().getIntent().getSerializableExtra(EXTRA_RESIDENCE_ID);
MyRentApp app = (MyRentApp) getActivity().getApplication();
portfolio = app.portfolio;
residence = portfolio.getResidence(resId);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.fragment_residence, parent, false);
getActivity().getActionBar().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;
}
else
if (requestCode == REQUEST_CONTACT)
{
String name = ContactHelper.getContact(getActivity(), data);
residence.tenant = name;
tenantButton.setText(name);
}
}
@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(), "", 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;
dateButton.setText(residence.getDateString());
}
}
Rename res/layout/activity_residence.xml to the more appropriate name: fragment_residence.xml.
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.FragmentActivity;
import android.support.v4.app.FragmentManager;
public class ResidenceListActivity extends FragmentActivity
{
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();
}
}
}
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;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
getActivity().setTitle(R.string.app_name);
MyRentApp app = (MyRentApp) getActivity().getApplication();
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.
Next we shall engage an external maps application.
<string name="show_map">Show Map</string>
<string name="geolocation">Geolocation</string>
You will already have renamed activity_residence.xml as fragment_residence.xml.
The following additions are required to the layout:
Here is the modified layout:
Filename: res/layout/fragment_residence.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<!-- LOCATION -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/location"
style="?android:listSeparatorTextViewStyle"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:baselineAligned="false">
<!-- Geolocation (GPS Coords) -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="60"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="@string/geolocation" />
<EditText
android:id="@+id/geolocation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:hint="@string/geolocation_hint" >
<requestFocus />
</EditText>
</LinearLayout>
<!-- Show Map Button -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="40"
android:orientation="vertical" >
<Button android:id="@+id/show_map"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/show_map"
android:layout_marginTop="16dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"/>
</LinearLayout>
</LinearLayout>
<!-- STATUS -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/status"
style="?android:listSeparatorTextViewStyle"/>
<Button android:id="@+id/registration_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"/>
<CheckBox
android:id="@+id/isrented"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:enabled="true"
android:focusable="false"
android:gravity="center"
android:text="@string/rented_checkbox_text"/>
<Button
android:id="@+id/tenant"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:text="@string/landlord" />
<Button android:id="@+id/residence_reportButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:text="@string/residence_report"/>
</LinearLayout>
The changes are evident in the following Figure 1.
Add the following method to IntentHelper:
public static void openPreferredLocationInMap(Activity parent, String location)
{
Uri geoLocation = Uri.parse("geo:0,0?").buildUpon().appendQueryParameter("q", location).build();
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(geoLocation);
if (intent.resolveActivity(parent.getPackageManager()) != null)
{
parent.startActivity(intent);
}
else
{
LogHelpers.info(parent, "Couldn't call " + location + ", no receiving apps installed!");
}
}
In the above code you will notice that we are using an implicit Intent.
Invoke this helper method from within ResidenceFragment by pressing the mapButton.
Here is the wiring to facilitate this. It should be added to ResidenceFragment:
Declare the map button:
private Button mapButton;
Add a map button listener:
mapButton = (Button) v.findViewById(R.id.show_map);
Register the map button listener:
mapButton .setOnClickListener(this);
Add a handler in the existing onClick(...) method:
case R.id.show_map : IntentHelper.openPreferredLocationInMap(getActivity(), residence.geolocation);
break;
The following import statement will be necessary:
import org.wit.android.helpers.IntentHelper;
Build and install the app on a phsical device or emulator: