March 3, 2012

Fragment Tutorial part 2


Janus App

Janus is a simple app designed to show how to use Fragments. In order to demonstrate how Fragments can be reused in multiple activities without changing code, this app shall have a (quite) different layout in portrait with respect to landscape.

Landscape orientation : we have a single activity containing two fragments, clicking on the button in MenuFragment, text in BodyFragment will change.


Portrait orientation : we have a main activity containing MenuFragment, after clicking on the button a secondary activity containing the BodyFragment and text to be changed will appear.


With some trivial changes, this approach is valid also for having different layout for smartphone and tablet.


How to place Fragments in layout

Using fragments the activity layout becomes much simpler, in fact in our example it contains only Fragments. Let's see our layouts for both orientations.

Landscape JanusActivity layout : ac_main.xml


Portrait JanusActivity layout : ac_main.xml

Portrait BodyActivity layout : ac_body.xml

And these are fragments layouts, quite conventional.

MenuFragment layout : fr_menu.xml

BodyFragment layout : fr_body.xml

You can see that, placing fragments layout in place of fragment object, you return to the old standard activity layout.



Java code for fragments and activities

We have four Java files, one for each class:

  • JanusActivity.java : java class for main activity (for both orientations)
  • BodyActivity.java : java class for secondary activity (only in portrait)
  • MenuFragment.java : java class for menu fragment
  • BodyFragment.java : java class for body fragment


When drawing layout, a fragment is very similar to views, while its java class has a lot in common with an activity. A fragment is tightly linked to the parent activity lifecycle, in fact there are a lot of callback functions (onAttach, onCreate, onActivityCreated, onCreateView) that are linked to the similar callbacks at activity level.


MenuFragment.java : contains code for button management and a listener interface, used to route button events to the parent activity (explained later)

package it.anddev.bradipao.janus;

import android.app.Activity;
import android.support.v4.app.Fragment;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

// extended from compatibility Fragment for pre-HC fragment support
public class MenuFragment extends Fragment {
   
   // views
   Button btn1,btn2;
   
   // activity listener
   private OnMenufragListener menufragListener;

   // interface for communication with activity
   public interface OnMenufragListener {
      public void onMenufrag(String s);
   }
   
   // onAttach
   @Override
   public void onAttach(Activity activity) {
      super.onAttach(activity);
      try {
         menufragListener = (OnMenufragListener) activity;
      } catch (ClassCastException e) {
         throw new ClassCastException(activity.toString()+" must implement OnMenufragListener");
      }
   }
   
   // onCreate
   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
   }

   // onActivityCreated
   @Override
   public void onActivityCreated(Bundle savedInstanceState) {
      super.onActivityCreated(savedInstanceState);
   }
   
   // onCreateView
   @Override
   public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState) {
      View view = inflater.inflate(R.layout.fr_menu,container,false);
      
      // get button BTN1
      btn1 = (Button)view.findViewById(R.id.btn1);
      // button listener
      btn1.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
            sendBodyTextToFragment("Text From Fragment");
         }
      });
      
      // get button BTN2
      btn2 = (Button)view.findViewById(R.id.btn2);
      // button listener
      btn2.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
            sendBodyTextToActivity("Text From Activity");
         }
      });
      
      return view;
   }
   
   // (recommended) method to send command to activity
   private void sendBodyTextToActivity(String s) {
      menufragListener.onMenufrag(s);
   }
   
   // alternate (not recommended) method with direct access to fragment
   private void sendBodyTextToFragment(String s) {
      
      // get body fragment (native method is getFragmentManager)
      BodyFragment fragment = (BodyFragment) getActivity().getSupportFragmentManager().findFragmentById(R.id.bodyFragment);
      
      // if fragment is not null and in layout, set text, else launch BodyActivity
      if ((fragment!=null)&&fragment.isInLayout()) {
         fragment.setText(s);
      } else {
         Intent intent = new Intent(getActivity().getApplicationContext(),BodyActivity.class);
         intent.putExtra("value",s);
         startActivity(intent);
      }
      
   }
   
}

BodyFragment.java : contains a public function to update text widget

package it.anddev.bradipao.janus;

import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

//extended from compatibility Fragment for pre-HC fragment support
public class BodyFragment extends Fragment {

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

   // onActivityCreated
   @Override
   public void onActivityCreated(Bundle savedInstanceState) {
      super.onActivityCreated(savedInstanceState);
   }
   
   // onCreateView
   @Override
   public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState) {
      View view = inflater.inflate(R.layout.fr_body,container,false);
      return view;
   }
   
   // set text
   public void setText(String item) {
      TextView view = (TextView) getView().findViewById(R.id.detailsText);
      view.setText(item);
   } 
   
}

JanusActivity.java : contains the listener implementation, used to call BodyFragment public function to update text widget; depending on orientation existing fragment is updated or secondary activity is invoked

package it.anddev.bradipao.janus;

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

// main activity (FragmentActivity provides fragment compatibility pre-HC)
public class JanusActivity extends FragmentActivity implements MenuFragment.OnMenufragListener {
   
   // called when the activity is first created
   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.ac_main);
   }
   
   // MenuFragment listener
   @Override
   public void onMenufrag(String s) {
      
      // get body fragment (native method is getFragmentManager)
      BodyFragment fragment = (BodyFragment) getSupportFragmentManager().findFragmentById(R.id.bodyFragment);
      
      // if fragment is not null and in layout, set text, else launch BodyActivity
      if ((fragment!=null)&&fragment.isInLayout()) {
         fragment.setText(s);
      } else {
         Intent intent = new Intent(this,BodyActivity.class);
         intent.putExtra("value",s);
         startActivity(intent);
      }
      
   }
   
}

BodyActivity.java : contains orientation detection logic, in order to go back to main activity in case of screen orientation change from portrait to landscape

package it.anddev.bradipao.janus;

import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

//created only when in portrait mode (FragmentActivity provides fragment compatibility pre-HC)
public class BodyActivity extends FragmentActivity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      
      // check orientation to avoid crash (this activity is not necessary in landscape)
      if (getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE) {
         finish();
         return;
      } else setContentView(R.layout.ac_body);
      
      // show body content as requested in Intent extra
      Bundle extras = getIntent().getExtras();
      if (extras != null) {
         // get data from Intent extra
         String s = extras.getString("value");
         // get body fragment
         BodyFragment fragment = (BodyFragment) getSupportFragmentManager().findFragmentById(R.id.bodyFragment);
         // if fragment is not null and in layout set text
         if ((fragment!=null)&&fragment.isInLayout()) {
            fragment.setText(s);
         }
      }

   }
   
}

Communicating between fragments

We said that when button in MenuFragment is clicked, text in BodyFragment shall be updated. In BodyFragment we have prepared the public function setText() in order to easily update the TextView from outside.

The easier (but not recommended) way to handle the Button click event and update TextView is to directly call the BodyFragment.setText() function from the MenuFragment button listener. From any fragment you can retrieve reference to any other fragment thanks to the getSupportFragmentManager() function.

A better (and recommended) way to do the same, is to generate an event for the activity listener and let the activity decide what to do. The finale code is quite similar to the previous one, the difference is that each fragment has not to be aware of the other one (no cross-fragment dependency), so in principle you could rewrite or even replace a fragment without changing code of the other fragments, only parent activity has to be adjusted.



Fragment Tutorial part 1


What is a Fragment?

Before Android HoneyComb (API 11) user interfaces were built managing activities and views, but with the introduction of tablets and bigger displays there were enough room for what were once contained into two or more activities. Fragments were introduced to better handle that additional user interface area.

The official definition for Fragments is the following:
A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running (sort of like a "sub activity" that you can reuse in different activities).
The very last part of the definition is a bit misleading. Instead of "sub activity", a Fragment is better defined as a "super view". In fact a Fragment is always inside an activity and behaves exactly like a View (can have setters, can generate events for its parent activity, is placed in the activity layout, and so on).


Why should I use Fragments?

Looking at current platform versions distribution, there are very few devices capable of natively support Fragments, they are more complex to use and conventional (pre-Fragment) user interface design is still completely supported by new devices, so why should I use Fragments now?

Simply put, it is an investment for the future. To be ready for time when mastering Fragments will be necessary to build a good app.

In order to be able to run Fragments also on pre-HoneyComb devices, a compatibility library called Support Package shall be imported in the project. This library provides compatibility with most of the new classes, so that you develop a single app and it runs everywhere.


When should I use Fragments?

It is difficult to give a simple rule for deciding when to use Fragments, but with some examples. In general if your app shall have a multi-pane layout, each pane should be implemented as a Fragment. But also for app with different layout for smartphone (single pane) and tablet (multi-pane), or with different layout for portrait (single-pane) and landscape (multi-pane).


Let's get ready for using Fragments with Support Package

As said before, if you want to use Fragments and provide compatibility through all Android versions, you have to implement them using the compatibility library (support package) in your project and build it.

  1. Locate Android SDK installation folder, and navigate to \extras\android\compatibility\v4 subfolder.
  2. Copy android-support-v4.jar in a folder named \libs under app root.
  3. Open app project in Eclipse, right-click on android-support-v4.jar and choose "Add to build path" in order to put the library in your app.

Now that Support Package is compiled in the project, you have to do the following when coding:

  1. When creating an Activity class that will contain Fragments, extend it from FragmentActivity instead of Activity. FragmentActivity provides Fragments support to pre-3.0 operating system versions.
  2. When creating a Fragment class extending it from Fragment, remember to import the compatible one (import android.support.v4.app.Fragment).


Fragment Tutorial - part 2 »

January 29, 2012

Blog Reboot

Ok, it's time reboot this blog.

Given my increased commitment with anddev.it forum, in italian language and completely devoted to android programming, it has soon became useless to replicate content in this blog. But there is the opportunity for an english blog, even if my english is far from being perfect.

So, forgive my english, code will speak for me. :-)