What is an Android Fragment?
Well as the name suggests it just a piece of something and in case of android its a piece of an Activity. Just like an activity the fragment also has a very similar life cycle onCreate(), onStart(), onStop(), onDestroy(), etc. In addition to that it has onCreateView() and onActivityCreated(). The system calls the onCreateView() when it's time for the fragment to draw its user interface for the first time. In this method you must inflate your fragment view return it. You can return null if the fragment does not provide a UI. The onActivityCreated() method is called when the fragment's activity has been created.
Why would we use a fragment?
You can create all your logic in an Activity and then manipulate the screen based on device size and orientation but the amount of code to programmatically draw views and implement all the logic based on layout and/or orientation will be make the whole project more complex than it needs to be. This is where fragments come to rescue, you can separate out the logic based on a functional entity rather than how the whole application looks to the end user. The concept is similar to the Portal technology where you create more than one portlet and put them on a given page.
The example
we are going to implement how to display a list view and a detail view displayed side by side when the user is in a landscape mode and in the case of portrait mode we just display the list and then when the user clicks on the given entry we display the detail view. The list entries in this tutorial are web URLs and actual web page is the detail view. The objective is to create the two fragments that are totaly independent and don't talk to each other which was done with the help of implementing an interface on the list activity. The activity is notified when the user select an URL and it alerts the detail fragment to updates its view. In addition to that we dynamically add and remove fragments from the activity based on the layout and the user action. So in the case of the portrait mode we only inflate the list view and then swap it out when the user clicks on the URL link whereas in the landscape mode we inflate both views the frame layouts. This example doesn't use another activity to display the detail page as some other tutorials have used, the objective is to have a simple and easy to understand tutorial that covers all the dynamics of the power of Android Fragments and how to use them effectively without complicating the matters.
Fragments in Portrait Mode
Fragments in Landscape Mode
Android Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.as400samplecode"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="15" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.Holo.Light">>
<activity
android:name=".AndroidFragmentActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
String values - strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Fragment Example</string>
<string name="detail_heading">Web Page Detail</string>
<string name="list_heading">Web Page Listing</string>
</resources>
Layout for Portrait mode - main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<FrameLayout
android:id="@+id/displayList"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</FrameLayout>
</LinearLayout>
Layout for Landscape mode - main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:baselineAligned="false"
android:orientation="horizontal" >
<FrameLayout
android:id="@+id/displayList"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" >
</FrameLayout>
<FrameLayout
android:id="@+id/displayDetail"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3" >
</FrameLayout>
</LinearLayout>
Listview XML resource - list_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="@string/list_heading"
android:textSize="20sp" />
<ListView android:id="@+id/listofURLs" android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Listview Row Layout - url_list.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dp"
android:textSize="16sp" >
</TextView>
Detail Web view XML resource - detail_view.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/detail_heading" android:padding="5dp"
android:textSize="20sp" />
<WebView
android:id="@+id/pageInfo"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Android fragment for the Webview - DetailFragment.java
package com.as400samplecode;
import android.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class DetailFragment extends Fragment {
String mURL = "";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v("DetailFragment", "onCreate()");
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.v("DetailFragment", "onActivityCreated()");
if (savedInstanceState != null) {
mURL = savedInstanceState.getString("currentURL", "");
}
if(!mURL.trim().equalsIgnoreCase("")){
WebView myWebView = (WebView) getView().findViewById(R.id.pageInfo);
myWebView.getSettings().setJavaScriptEnabled(true);
myWebView.setWebViewClient(new MyWebViewClient());
myWebView.loadUrl(mURL.trim());
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("currentURL", mURL);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.v("DetailFragment", "onCreateView()");
View view = inflater.inflate(R.layout.detail_view, container, false);
return view;
}
public void setURLContent(String URL) {
mURL = URL;
}
public void updateURLContent(String URL) {
mURL = URL;
WebView myWebView = (WebView) getView().findViewById(R.id.pageInfo);
myWebView.getSettings().setJavaScriptEnabled(true);
myWebView.setWebViewClient(new MyWebViewClient());
myWebView.loadUrl(mURL.trim());
}
private class MyWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return false;
}
}
}
Android fragment for the Listview - ListFragment.java
package com.as400samplecode;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
public class ListFragment extends Fragment {
OnURLSelectedListener mListener;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v("ListFragment", "onCreate()");
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Log.v("ListFragment", "onActivityCreated().");
Log.v("ListsavedInstanceState", savedInstanceState == null ? "true" : "false");
//Generate list View from ArrayList
displayListView();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.v("ListFragment", "onCreateView()");
Log.v("ListContainer", container == null ? "true" : "false");
Log.v("ListsavedInstanceState", savedInstanceState == null ? "true" : "false");
if (container == null) {
return null;
}
View view = inflater.inflate(R.layout.list_view, container, false);
return view;
}
// Container Activity must implement this interface
public interface OnURLSelectedListener {
public void onURLSelected(String URL);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnURLSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnURLSelectedListener");
}
}
private void displayListView() {
//Array list of countries
List<String> urlList = new ArrayList<String>();
urlList.add("http://www.google.com");
urlList.add("http://mail.google.com");
urlList.add("http://maps.google.com");
//create an ArrayAdaptar from the String Array
ArrayAdapter<String> dataAdapter = new ArrayAdapter<String>(getActivity(),
R.layout.url_list, urlList);
ListView listView = (ListView) getView().findViewById(R.id.listofURLs);
// Assign adapter to ListView
listView.setAdapter(dataAdapter);
//enables filtering for the contents of the given ListView
listView.setTextFilterEnabled(true);
listView.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// Send the URL to the host activity
mListener.onURLSelected(((TextView) view).getText().toString());
}
});
}
}
Android activity for the application - AndroidFragmentActivity.java
package com.as400samplecode;
import com.as400samplecode.ListFragment.OnURLSelectedListener;
import android.app.Activity;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.util.Log;
public class AndroidFragmentActivity extends Activity
implements OnURLSelectedListener {
boolean detailPage = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.v("AndroidFragmentActivity", "onCreate()");
Log.v("AndroidFragmentsavedInstanceState", savedInstanceState == null ? "true" : "false");
setContentView(R.layout.main);
if(savedInstanceState == null) {
FragmentTransaction ft = getFragmentManager().beginTransaction();
ListFragment listFragment = new ListFragment();
ft.add(R.id.displayList, listFragment, "List_Fragment");
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
if(findViewById(R.id.displayDetail) != null){
detailPage = true;
getFragmentManager().popBackStack();
DetailFragment detailFragment = (DetailFragment) getFragmentManager().findFragmentById(R.id.displayDetail);
if(detailFragment == null){
FragmentTransaction ft = getFragmentManager().beginTransaction();
detailFragment = new DetailFragment();
ft.replace(R.id.displayDetail, detailFragment, "Detail_Fragment1");
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
}
}
@Override
public void onURLSelected(String URL) {
Log.v("AndroidFragmentActivity",URL);
if(detailPage){
DetailFragment detailFragment = (DetailFragment)
getFragmentManager().findFragmentById(R.id.displayDetail);
detailFragment.updateURLContent(URL);
}
else{
DetailFragment detailFragment = new DetailFragment();
detailFragment.setURLContent(URL);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.displayList, detailFragment, "Detail_Fragment2");
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(null);
ft.commit();
}
}
}
No comments: