Chapter06-Fragments Publish
Chapter06-Fragments Publish
FRAGMENT
CONTENTS
INTRODUCTION
FRAGMENT
OPERATIONS ON FRAGMENTS
INTRODUCTION
(Designing complex Android apps)
A major goal of the Android platform is to consistently offer a pleasant, rich, intuitive, and
homogeneous user-experience.
This is a challenge for the designers. Your apps need to provide a sense of sameness and
familiarity as well as a touch of their ‘unique personality’.
A set of recommendations called Material Design is suggested to adopters of SDK5+ as a way to
create apps within the boundaries of a common modeling framework.
By adopting those suggestions the “look-and-feel” of all new apps is expected to become more
uniform and its navigation more predictable.
In this lesson we will explore some of the building blocks used to implement this design vision
INTRODUCTION (FRAGMENTS)
Android is a multitasking OS and its hardware specs allow for real parallelism. However, at any given time only one
activity per app can be ‘visible’ and ‘active’. This fact is rather limiting considering the extensive screen area
offered by larger devices (tablets, phablets, TV sets, etc). Fragments offer an escape solution.
The Fragment class produces visual objects that can be dynamically attached to designated portions of the app’s
GUI. Each fragment object can expose its own views and provide means for the users to interact with the
application.
Fragments must exist within the boundaries of an Activity that acts as a ‘home-base’ or host.
A host activity’s GUI may expose any number of fragments. In this GUI each fragment could be visible and active.
Fragments behave as independent threads, usually they cooperate in achieving a common goal; however each can
run its own I/O, events and business logic.
Fragments could access ‘global data’ held in the main activity to which they belong. Likewise, they could send
values of their own to the main activity for potential dissemination to other fragments.
Fragments have their own particular Life-Cycle, in which the onCreateView method does most of the work needed
to make them.
Fragments were first introduced in the Honeycomb SDK (API 11)
INTRODUCTION (FRAGMENTS)
A possible arrangement of Fragments attached to the main GUI of an app
ACTIVITY (Main Host Container)
Fragment3 (View 3)
Fragment1 (View 1)
Fragment2 (View 2)
FRAGMENTS
(LIFECYCLE)
onAttach() Invoked when the fragment has been connected
to the host activity.
onCreate() Used for initializing non-visual components
needed by the fragment.
onCreateView() Most of the work is done here. Called to
create the view hierarchy representing the fragment. Usually
inflates a layout, defines listeners, and populates the widgets
in the inflated layout.
onPause() The session is about to finish. Here you should
commit state data changes that are needed in case the
fragment is reexecuted.
onDetach() Called when the inactive fragment is
disconnected from the activity.
FRAGMENTS
(Inter-fragment communication)
All Fragment-to-Fragment communication is done in a
centralized mode through the home-base Activity.
Activity
Listener(s) & Dispatcher(s) As a design principle; two Fragments should NEVER
communicate directly.
The home-base Activity and its fragments interact through
listeners and events.
Fragment1 When a fragment has some data for another fragment, it
Event-send-msg sends it to a listener in the main which in turn dispatches
Event-receive-msg
to a listener of the second fragment.
Fragment2
Event-send-msg
Event-receive-msg
FRAGMENTS
(Integrating the home Activity and its fragments)
In general fragments appear on their enclosing Activity’s GUI using one of the following
attachment approaches
◦ Dynamic Binding: The main activity defines a particular place on its GUI for fragments to be plugged in
(or attached). Occupancy of designated areas is not permanent. Later on, the hosting Activity may
replace a fragment with another (see Example-1)
◦ Static Binding: the Activity’s GUI declares a portion of its layout as a < fragment > and explicitly supplies
a reference to the first type of fragment to be held there using the “android:name=fragmentName”
clause. This simple association does not require you to call the constructors (or pass initial parameters).
A static binding is permanent, fragments cannot be replaced at run time (see Example-2)
◦ Multiple Fragments: the hosting activity may simultaneously expose any number of fragments using a
combination of the strategies describe above. Fragments may interact with each other using the
enclosing activity as a central store-and-forward unit (Example-3).
FRAGMENTS (DYNAMIC BINDING)
Fragments must be created inside a secure FragmentTransaction block.
You may use the method add() to aggregate a fragment to the activity. Optionally any view
produced by the fragment is moved into an UI container of the host activity.
When you use the replace method to refresh the UI, the current view in the target area is
removed and the new fragment is added to the activity’s UI.
A faceless fragment may also be added to an activity without having to produce a view hierarchy.
STEPS
◦ 1. Obtain a reference to the FragmentManager, initiate a transaction:
FragmentTransaction ft= getFragmentManager().beginTransaction();
◦ 2. Create an instance of your fragment, supply arguments if needed:
FragmentBlue blueFragment= FragmentBlue.newInstance(“some-value”);
◦ 3. Place the fragment’s view on the application’s GUI: ft.replace(R.id.main_holder_blue, blueFragment);
◦ 4. Terminate the transaction: ft.commit();
FRAGMENTS
(DYNAMIC BINDING)
Example of dynamic binding. Instances of the
FragmentRed and FragmentBlue classes are
created at run-time and set to replace the left
and right portions of the app’s GUI.
// create a new BLUE fragment - show it
ft = getFragmentManager().beginTransaction();
blueFragment = FragmentBlue.newInstance(“new-blue”);
ft.replace(R.id.main_holder_blue, blueFragment);
ft.commit();
// create a new RED fragment - show it
ft = getFragmentManager().beginTransaction();
redFragment = FragmentRed.newInstance(“new-red”);
ft.replace(R.id.main_holder_red, redFragment);
main_holder_blue main_holder_red
ft.commit();
FRAGMENTS
(DYNAMIC BINDING)
This example shows a master-detail design. It is based on
three classes:
◦ MainActivity (host),
◦ FragmentRed (master) and
◦ FragmentBlue (detail)
FragmentTransaction ft = getFragmentManager().beginTransaction();
redFragment = FragmentRed.newInstance(intValue);
ft.add(R.id.main_holder, redFragment, “RED-TAG”);
ft.hide(redFragment);
ft.show(redFragment);
ft.detach(redFragment);
ft.attach(redFragment);
ft.commit();
OPERATIONS ON FRAGMENTS
(Using the BackStack to recreate state)
Android-OS introduced a special stack to help fragments keep state when the user navigates from one
UI to the other.
The artifact is called the BackStack and allows push/pop operations to manage FragmentTransactions.
The BackStack mirrors the behavior of the
activity stack within a single activity
Remember that all Android devices include a Back button. When this button is pressed in succession
the app transitions to the previous screen shown by the app until it ends. This mechanism provides a
natural historical navigation (also known as Back-Navigation). Another important pattern of navigation
known as Child-to-HighAncestor is discussed later.
Why should BackStack be used?
When the BackStack is used, the retrieved fragment is re-used (instead of recreated from scratch) and
its state data transparently restored (no need for input/output state bundles). This approach leads to
simpler and more efficient apps.
OPERATIONS ON FRAGMENTS
(Using the BackStack to recreate state)
A typical sequence to create a fragment and add it to the stack follows:
FragmentTransaction ft = getFragmentManager().beginTransaction();
Fragment redFragment = FragmentRed.newInstance(intParameterValue);
ft.replace(R.id.main_holder, redFragment, “RED-FRAG”);
ft.addToBackStack(“RED_UI”);
ft.commit();
In this example a fragment transaction (ft) adds a redFragment to the main activity’s UI. The
fragment uses the optional tag/alias “RED-FRAG”, as an alternative form of identification. Later,
we may inspect the app’s UI, and find the ‘alias’ of the fragment held inside the main_holder
container.
Before the transaction commits, the statement: ft.addToBackStack(“RED_UI”); pushes a reference
of the current transaction’s environment in the BackStack including the optional identification
tag: “RED_UI”. Later on, we may search through the BackStack looking for an entry matching the
tag value. When found and popped, it resets the UI to the state held by that transaction.
OPERATIONS ON FRAGMENTS
(Using the BackStack to recreate state)
Navigation
Retrieving entries from the BackStack can be done in various ways, such as:
◦ Pressing the Back button to trigger a historical navigation exposing in succession the previous User-Interfaces.
◦ Invoking the method .popBackStackImmediate(…) to selectively restore any particular BackStackEntry holding an UI already shown to the user.
In this transaction we reproduce the behavior of the Back key when used for historical navigation.
◦ 1. The size of the BackStack is determined ( getBackStackEntryCount)
◦ 2. The top element of the stack is inspected. Firstly we obtain its tag and later its numerical id by calling the method:
fragmentManager.getBackStackEntryAt(bsCount-1).getId().
◦ 3. The . popBackStack(id, 1) method removes BackStackEntries from the top of the BackStack until it finds the entry matching the supplied id. At
this point the app’s UI is updated showing the screen associated to the matching transaction previously held in the stack.
// Remove current fragment’s UI and show its previous screen
try {
FragmentTransaction ft = getFragmentManager().beginTransaction();
android.app.FragmentManager fragmentManager = getFragmentManager();
1 int bsCount = fragmentManager.getBackStackEntryCount();
String tag = fragmentManager.getBackStackEntryAt(bsCount-1).getName();
2 int id = fragmentManager.getBackStackEntryAt(bsCount-1).getId();
Log.e(“PREVIOUS Fragment: ”, “” + tag + “ ” + id);
3 fragmentManager.popBackStackImmediate(id, 1); //supply: id or tag
ft.commit();
}
catch (Exception e) { Log.e(“REMOVE>>> ”, e.getMessage() ); }
OPERATIONS ON FRAGMENTS
(Using the BackStack to recreate state)
Navigating Through the BackStack
The following code clears the current BackStack. All fragment transactions pushed by calling the
method ft.addToBackStack (…) are deleted. The app’ UI is updated, removing all screens shown by
fragments that put a reference to themselves in the BackStack.
This approach could be used to provide Child-to-HighAncestor navigation.
try {
FragmentTransaction ft = getFragmentManager().beginTransaction();
android.app.FragmentManager fragmentManager = getFragmentManager();
fragmentManager.popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.commit();
}
catch (Exception e) { Log.e(“CLEAR-STACK>>> ”, e.getMessage() ); }
OPERATIONS ON
FRAGMENTS (EXAMPLE)
1. A new redFragment is created. Its view is attached to the
activity’s UI using the add( ) method. Finally its enclosing
transaction is pushed on the BackStack.
2. As above, however; the fragment’s view is attached to the
activity’s UI using the replace() method (old view is destroyed).
The current transaction is also added to the BackStack.
3. Popping an entry from the BackStack removes the current
app’s UI and navigates back to the previously stored fragment’s
view. State data (if any) is shown as it was before leaving the
view. The size of BackStack is reduced by one
4. The “Remove” button activates a findFragmentByTag search.
This first searches through fragments that are currently added to
the manager's activity; if no such fragment is found, then all
fragments currently on the back stack are searched. In our
example, the current view is retired from the UI using remove()
and the historically previous UI is presented.
OPERATIONS ON FRAGMENTS (EXAMPLE)
1. A new redFragment is
created and its enclosing
transaction is added to the
BackStack.
2. Pressing the Back button
removes the current fragment
from the UI and Back-
Navigates to the previous
fragment. Its state is
preserved, so you do not need
to refill its widgets.
OPERATIONS ON FRAGMENTS
(Example – layout main_activity)
<?xml version=“1.0” encoding=“utf-8”?>
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android” android:layout_width=“match_parent”
android:layout_height=“match_parent” android:baselineAligned=“false”
android:orientation=“vertical” android:padding=“10dp” >
<TextView android:id=“@+id/textView1Main” android:layout_width=“match_parent”
android:layout_height=“wrap_content” android:background=“#77ffff00”
android:text=“Main Layout ...” android:textAppearance=“?android:attr/textAppearanceLarge” />
<LinearLayout android:layout_width=“match_parent” android:layout_height=“wrap_content”
android:baselineAligned=“false” android:orientation=“horizontal” >
<Button android:id=“@+id/button1MainShowRed” android:layout_width=“150dp”
android:layout_height=“wrap_content” android:layout_weight=“1” android:text=“ADD new RedFragment” />
<Button android:id=“@+id/button2MainPop” android:layout_width=“150dp”
android:layout_height=“wrap_content” android:layout_weight=“1” android:text=“POP Trans BackStack” />
</LinearLayout>
<LinearLayout android:layout_width=“match_parent” android:layout_height=“wrap_content”
android:baselineAligned=“false” android:orientation=“horizontal” >
<Button android:id=“@+id/button4MainReplace” android:layout_width=“150dp”
android:layout_height=“wrap_content” android:layout_weight=“1” android:text=“REPLACE new RedFragment” />
<Button android:id=“@+id/button3MainRemove” android:layout_width=“150dp”
android:layout_height=“wrap_content” android:layout_weight=“1” android:text=“REMOVE RedFragment” />
</LinearLayout>
<FrameLayout android:id=“@+id/main_holder” android:layout_width=“match_parent”
android:layout_height=“wrap_content” android:layout_weight=“2” android:orientation=“vertical” />
</LinearLayout>
OPERATIONS ON FRAGMENTS
(Example – layout_red/MainCallbacks)
<?xml version=“1.0” encoding=“utf-8”?>
<LinearLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:id=“@+id/layout_red”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“vertical” >
<TextView android:id=“@+id/textView1Red” android:layout_width=“match_parent”
android:layout_height=“175dp” android:layout_margin=“20dp”
android:background=“#ffff0000” android:gravity=“center”
android:text=“Red Layout...” android:textColor=“@android:color/white”
android:textSize=“35sp” android:textStyle=“bold” />
<Button android:id=“@+id/button1Red” android:layout_width=“match_parent”
android:layout_height=“wrap_content” android:layout_marginLeft=“20dp”
android:layout_marginRight=“20dp” android:text=“Change Red Label” />
</LinearLayout>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txtMsg = (TextView) findViewById(R.id.textView1Main);
btnAddRedFragment = (Button) findViewById(R.id.button1MainShowRed);
btnReplaceRedFragment = (Button) findViewById(R.id.button4MainReplace);
btnPop = (Button) findViewById(R.id.button2MainPop);
btnRemove = (Button) findViewById(R.id.button3MainRemove);
btnAddRedFragment.setOnClickListener(this);
btnReplaceRedFragment.setOnClickListener(this);
btnPop.setOnClickListener(this);
btnRemove.setOnClickListener(this);
}
// CallBack (receiving messages coming from Fragments)
@Override
public void onMsgFromFragToMain(String sender, String strValue) {/* show message arriving to MainActivity*/ txtMsg.setText( sender + “=>” + strValue ); }
OPERATIONS ON FRAGMENTS
(Example – MainActivity)
public void onClick(View v) {
if(v.getId() == btnAddRedFragment.getId() ) addRedFragment(++serialCounter);
if(v.getId() == btnReplaceRedFragment.getId() ) replaceRedFragment(++serialCounter);
if(v.getId() == btnPop.getId() ){
androidx.fragment.app.FragmentManager fragmentManager = getSupportFragmentManager();
int counter = fragmentManager.getBackStackEntryCount();
txtMsg.setText(“BACKSTACK old size=” + counter);
if(counter>0) { //VERSION 1 [popBackStack could be used as opposite of addBackStack()]
// pop takes a Transaction from the BackStack and a view is also deleted
fragmentManager.popBackStackImmediate(); txtMsg.append(“\nBACKSTACK new size=” + fragmentManager.getBackStackEntryCount() );
}
}//Pop
if(v.getId() == btnRemove.getId() ){
FragmentManager fragmentManager = getSupportFragmentManager();
int counter = fragmentManager.getBackStackEntryCount();
txtMsg.setText(“BACKSTACK old size=” + counter);
//VERSION 2: removes an existing fragment from fragmentTransaction. If it was added to a container, its view is also removed from that container. BackStack may remain the same
Fragment f1 = fragmentManager.findFragmentByTag(“RED-TAG”);
fragmentManager.beginTransaction().remove(f1).commit(); txtMsg.append(“\nBACKSTACK new size=” + fragmentManager.getBackStackEntryCount() );
// VERSION 3
// Fragment f1 = fragmentManager.findFragmentById(R.id.main_holder);
// fragmentManager.beginTransaction().remove(f1).commit(); txtMsg.append(“\nBACKSTACK new size=” + fragmentManager.getBackStackEntryCount() );
}//Remove
}//onClick
OPERATIONS ON FRAGMENTS
(Example – MainActivity)
@Override
public void onBackPressed() { super.onBackPressed(); txtMsg.setText(“BACKSTACK size=” + getSupportFragmentManager().getBackStackEntryCount()); }
public void addRedFragment(int intValue) {
// create a new RED fragment, add fragment to the transaction
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
redFragment = FragmentRed.newInstance(intValue);
ft.add(R.id.main_holder, redFragment, “RED-TAG”);
ft.addToBackStack(“MYSTACK1”);
ft.commit();
// complete any pending insertions in the BackStack, then report its size
getSupportFragmentManager().executePendingTransactions(); txtMsg.setText(“BACKSTACK size =” + getSupportFragmentManager().getBackStackEntryCount() );
}
public void replaceRedFragment(int intValue) {
// create a new RED fragment, replace fragments in the transaction
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
redFragment = FragmentRed.newInstance(intValue);
ft.replace(R.id.main_holder, redFragment, “RED-TAG”);
ft.addToBackStack(“MYSTACK1”);
ft.commit();
// complete any pending insertions in the BackStack, then report its size
getSupportFragmentManager().executePendingTransactions();
txtMsg.setText(“BACKSTACK size =” + getSupportFragmentManager().getBackStackEntryCount() );
}
} //end activity
OPERATIONS ON FRAGMENTS
(Example – FragmentRed)
public class FragmentRed extends Fragment {
MainActivity main; TextView txtRed; Button btnRedClock; int fragmentId; String selectedRedText = “”;
public static FragmentRed newInstance(int fragmentId) {
FragmentRed fragment = new FragmentRed();
Bundle bundle = new Bundle(); bundle.putInt(“fragmentId”, fragmentId); fragment.setArguments(bundle);
return fragment;
}// newInstance
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Activities containing this fragment must implement MainCallbacks
if (!(getActivity() instanceof MainCallbacks)) throw new IllegalStateException(“>>> Activity must implement MainCallbacks”);
main = (MainActivity) getActivity();
fragmentId = getArguments().getInt(“fragmentId”, -1);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
LinearLayout view_layout_red = (LinearLayout) inflater.inflate(R.layout.layout_red, null);
txtRed = (TextView) view_layout_red.findViewById(R.id.textView1Red); txtRed.setText(“Fragment ” + fragmentId );
btnRedClock = (Button) view_layout_red.findViewById(R.id.button1Red);
btnRedClock.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { txtRed.append(“\nRed Clock:\n” + new Date().toString()); }});
return view_layout_red;
}
}// FragmentRed