Understand lifecycle of Activity and Fragment, Introduction

To develop Android Apps run on Fragment, understanding of lifecycle of Activity and Fragment is very important. (I'm confused now!)

It is a simple exercise to load Fragment using FragmentTransaction, with all lifecycle methods overrided to display the status on Toast and output to Log. Such that you can easy understand the lifecycle behavior between Activity and Fragment.

Understand lifecycle of Activity and Fragment


MainActivity.java
package com.example.androidfragmenttest;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.Toast;
import android.app.Activity;

public class MainActivity extends FragmentActivity {

static public class MyFragment1 extends Fragment {

String savedText;

@Override
public void onAttach(Activity activity) {
toastFragment1Method();
super.onAttach(activity);
}

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

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
toastFragment1Method();
View view = inflater.inflate(R.layout.fragment_layout1, null);
return view;
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
toastFragment1Method();
super.onActivityCreated(savedInstanceState);
}

@Override
public void onStart() {
toastFragment1Method();
super.onStart();
}

@Override
public void onResume() {
toastFragment1Method();
super.onResume();
}

@Override
public void onPause() {
toastFragment1Method();
super.onPause();
}

@Override
public void onStop() {
toastFragment1Method();
super.onStop();
}

@Override
public void onDestroyView() {
toastFragment1Method();
super.onDestroyView();
}

@Override
public void onDestroy() {
toastFragment1Method();
super.onDestroy();
}

@Override
public void onDetach() {
toastFragment1Method();
super.onDetach();
}

private void toastFragment1Method(){
StackTraceElement[] s = Thread.currentThread().getStackTrace();
String methodName = s[3].getMethodName();
Toast.makeText(getActivity(),
"MyFragment1 : " + methodName, Toast.LENGTH_SHORT).show();

Log.i("MYTAG", "MyFragment1 : " + methodName);
}

}

FrameLayout fragmentContainer;

@Override
protected void onCreate(Bundle savedInstanceState) {
toastActivityMethod();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

fragmentContainer = (FrameLayout)findViewById(R.id.container);
if(savedInstanceState == null){
//if's the first time created
MyFragment1 myListFragment1 = new MyFragment1();
FragmentManager supportFragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
fragmentTransaction.add(R.id.container, myListFragment1);
fragmentTransaction.commit();
}

}

@Override
protected void onStart() {
toastActivityMethod();
super.onStart();
}

@Override
protected void onResume() {
toastActivityMethod();
super.onResume();
}

@Override
protected void onPause() {
toastActivityMethod();
super.onPause();
}

@Override
protected void onStop() {
toastActivityMethod();
super.onStop();
}

@Override
protected void onDestroy() {
toastActivityMethod();
super.onDestroy();
}

private void toastActivityMethod(){
StackTraceElement[] s = Thread.currentThread().getStackTrace();
String methodName = s[3].getMethodName();
Toast.makeText(getApplicationContext(),
"MainActivity : " + methodName, Toast.LENGTH_SHORT).show();

Log.i("MYTAG", "MainActivity : " + methodName);
}

}


/res/layout/activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >

<FrameLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</FrameLayout>

</RelativeLayout>


/res/layout/fragment_layout1.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Fragment 1"/>

</LinearLayout>




download filesDownload the files.

To view the Log in Eclipse, Show View of LogCat in Window menu; select info and enter tag:MYTAG in the search box.



Suggested Android Developers doc:
- Activity
- Fragments





Next:


The New Digital Age: Reshaping the Future of People, Nations and Business


In an unparalleled collaboration, two leading global thinkers in technology and foreign affairs give us their widely anticipated, transformational vision of the future: a world where everyone is connected—a world full of challenges and benefits that are ours to meet and to harness.

Eric Schmidt is one of Silicon Valley’s great leaders, having taken Google from a small startup to one of the world’s most influential companies. Jared Cohen is the director of Google Ideas and a former adviser to secretaries of state Condoleezza Rice and Hillary Clinton. With their combined knowledge and experiences, the authors are uniquely positioned to take on some of the toughest questions about our future: Who will be more powerful in the future, the citizen or the state? Will technology make terrorism easier or harder to carry out? What is the relationship between privacy and security, and how much will we have to give up to be part of the new digital age?

In this groundbreaking book, Schmidt and Cohen combine observation and insight to outline the promise and peril awaiting us in the coming decades. At once pragmatic and inspirational, this is a forward-thinking account of where our world is headed and what this means for people, states and businesses.

With the confidence and clarity of visionaries, Schmidt and Cohen illustrate just how much we have to look forward to—and beware of—as the greatest information and technology revolution in human history continues to evolve. On individual, community and state levels, across every geographical and socioeconomic spectrum, they reveal the dramatic developments—good and bad—that will transform both our everyday lives and our understanding of self and society, as technology advances and our virtual identities become more and more fundamentally real.

As Schmidt and Cohen’s nuanced vision of the near future unfolds, an urban professional takes his driverless car to work, attends meetings via hologram and dispenses housekeeping robots by voice; a Congolese fisherwoman uses her smart phone to monitor market demand and coordinate sales (saving on costly refrigeration and preventing overfishing); the potential arises for “virtual statehood” and “Internet asylum” to liberate political dissidents and oppressed minorities, but also for tech-savvy autocracies (and perhaps democracies) to exploit their citizens’ mobile devices for ever more ubiquitous surveillance. Along the way, we meet a cadre of international figures—including Julian Assange—who explain their own visions of our technology-saturated future.

Inspiring, provocative and absorbing, The New Digital Age is a brilliant analysis of how our hyper-connected world will soon look, from two of our most prescient and informed public thinkers.

Simulate kill activity to test onSaveInstanceState() / onCreate()

In order to test the lifecycle behaviour of our apps, we always have to leave our apps to move it to background, wait sometime for the system to kill it...and restart it.

It's a alternative to simulate "kill activity" by the Immediately destroy activities (or called Don't keep activities/Do not keep activities) selection Development Settings:

Select Developer options in Setting
Select Developer options in Setting

Select Advanced

Check Don't keep activities


According to the Android Developer document of Using the Dev Tools App:

Immediately destroy activities
Tells the system to destroy an activity as soon as it is stopped (as if Android had to reclaim memory). This is very useful for testing the onSaveInstanceState(Bundle) / onCreate(android.os.Bundle) code path, which would otherwise be difficult to force. Choosing this option will probably reveal a number of problems in your application due to not saving state. For more information about saving an activity's state, see the Activities document.


Notice: This setting for developers only, NOT for general user.



Related: The lifecycle in Activity Killed

Embed WebView in Fragment

This exercise embed WebView in Fragment.

Embed WebView in Fragment


package com.example.androidwebviewfragment;

import android.os.Bundle;
import android.app.Activity;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.webkit.WebViewClient;

public class MainActivity extends Activity {

static public class MyWebViewFragment extends Fragment {

WebView myWebView;
final static String myBlogAddr = "http://android-er.blogspot.com";
String myUrl;


@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_webfragment, container, false);
myWebView = (WebView)view.findViewById(R.id.mywebview);

myWebView.getSettings().setJavaScriptEnabled(true);
myWebView.setWebViewClient(new MyWebViewClient());

if(myUrl == null){
myUrl = myBlogAddr;
}
myWebView.loadUrl(myUrl);

return view;

}

private class MyWebViewClient extends WebViewClient {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
myUrl = url;
view.loadUrl(url);
return true;
}
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(true);
}

}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@Override
public void onBackPressed() {
MyWebViewFragment fragment =
(MyWebViewFragment)getFragmentManager().findFragmentById(R.id.myweb_fragment);
WebView webView = fragment.myWebView;

if(webView.canGoBack()){
webView.goBack();
}else{
super.onBackPressed();
}
}

}


Layout, activity_main.xml.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >

<fragment
android:name="com.example.androidwebviewfragment.MainActivity$MyWebViewFragment"
android:id="@+id/myweb_fragment"
android:layout_height="match_parent"
android:layout_width="match_parent" />

</RelativeLayout>


Layout of our fragment, layout_webfragment.xml.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<WebView
android:id="@+id/mywebview"
android:layout_height="match_parent"
android:layout_width="match_parent" />

</LinearLayout>


Permission of "android.permission.INTERNET" is need.

- I can't implement using WebViewFragment, so this exercise extends Fragment, not WebViewFragment.
- In this implement, the WebView can load the last loaded address after orientation changed, but cannot keep the navigation history.

download filesDownload the files.

Implement animation in FragmentTransaction

setCustomAnimations(int enter, int exit, int popEnter, int popExit) and setCustomAnimations(int enter, int exit) methods of FragmentTransaction class set specific animation resources to run for the fragments that are entering and exiting in this transaction. The popEnter and popExit animations will be played for enter/exit operations specifically when popping the back stack.

Example Code:

    FragmentTransaction fragmentTransaction =
getActivity().getFragmentManager().beginTransaction();

fragmentTransaction.setCustomAnimations(
R.anim.fadein, R.anim.fadeout, R.anim.fadein, R.anim.fadeout);

fragmentTransaction.replace(R.id.phone_container, myDetailFragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();


animation in FragmentTransaction


Further work on previous exercise "implements OnBackStackChangedListener to display UP icon, automatically by getBackStackEntryCount()".

Create /res/anim/fadein.xml.
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator"
android:propertyName="alpha"
android:valueFrom="0.0"
android:valueTo="1"
android:duration="500"
/>


/res/anim/fadeout.xml.
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator"
android:propertyName="alpha"
android:valueFrom="1"
android:valueTo="0.0"
android:duration="500"
/>


MainActivity.java
package com.example.androiddualmode;

import android.os.Bundle;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager.OnBackStackChangedListener;
import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity implements OnBackStackChangedListener{

// if run on phone, isSinglePane = true
// if run on tablet, isSinglePane = false
static boolean isSinglePane;

static String[] month ={
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"};

public static class MyListFragment extends ListFragment {

@Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);

ListAdapter myArrayAdapter =
new ArrayAdapter<String>(
getActivity(), android.R.layout.simple_list_item_1, month);
setListAdapter(myArrayAdapter);

}

@Override
public void onListItemClick(ListView l, View v, int position, long id) {

String clickedDetail = (String)l.getItemAtPosition(position);

if(isSinglePane == true){
/*
* The second fragment not yet loaded.
* Load MyDetailFragment by FragmentTransaction, and pass
* data from current fragment to second fragment via bundle.
*/
MyDetailFragment myDetailFragment = new MyDetailFragment();
Bundle bundle = new Bundle();
bundle.putString("KEY_DETAIL", clickedDetail);
myDetailFragment.setArguments(bundle);
FragmentTransaction fragmentTransaction =
getActivity().getFragmentManager().beginTransaction();

fragmentTransaction.setCustomAnimations(
R.anim.fadein, R.anim.fadeout, R.anim.fadein, R.anim.fadeout);

fragmentTransaction.replace(R.id.phone_container, myDetailFragment);

/*
* Add this transaction to the back stack.
* This means that the transaction will be remembered after it is
* committed, and will reverse its operation when later popped off
* the stack.
*/
fragmentTransaction.addToBackStack(null);

fragmentTransaction.commit();

}else{
/*
* Activity have two fragments. Pass data between fragments
* via reference to fragment
*/

//get reference to MyDetailFragment
MyDetailFragment myDetailFragment =
(MyDetailFragment)getFragmentManager().findFragmentById(R.id.detail_fragment);
myDetailFragment.updateDetail(clickedDetail);

}
}

}

public static class MyDetailFragment extends Fragment {

TextView textDetail;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View view = inflater.inflate(R.layout.layout_detailfragment, null);
textDetail = (TextView)view.findViewById(R.id.text_detail);

Bundle bundle = getArguments();
if(bundle != null){
String detail = bundle.getString("KEY_DETAIL", "no argument pass");
textDetail.setText(detail);
}

return view;
}

public void updateDetail(String detail) {
textDetail.setText(detail);
}

}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

View v = findViewById(R.id.phone_container);
if(v == null){
//it's run on tablet
isSinglePane = false;
/*
* MyListFragment and MyDetailFragment have been loaded in XML,
* no need load.
*/

}else{
//it's run on phone
//Load MyListFragment programmatically
isSinglePane = true;

if(savedInstanceState == null){
//if's the first time created
MyListFragment myListFragment = new MyListFragment();
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();

fragmentTransaction.add(R.id.phone_container, myListFragment);
fragmentTransaction.commit();
}
}

getFragmentManager().addOnBackStackChangedListener(this);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
getFragmentManager().popBackStack();
return true;
}

return super.onOptionsItemSelected(item);
}

final static String KEY_DISPLAY_OPT = "KEY_Display_Option";

@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
outState.putInt(KEY_DISPLAY_OPT, getActionBar().getDisplayOptions());
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onRestoreInstanceState(savedInstanceState);
int savedDisplayOpt = savedInstanceState.getInt(KEY_DISPLAY_OPT);
if(savedDisplayOpt != 0){
getActionBar().setDisplayOptions(savedDisplayOpt);
}
}

@Override
public void onBackStackChanged() {
int backStackEntryCount = getFragmentManager().getBackStackEntryCount();
if(backStackEntryCount > 0){
getActionBar().setDisplayHomeAsUpEnabled(true);
}else{
getActionBar().setDisplayHomeAsUpEnabled(false);
}
}

}


download filesDownload the files.

Google Play content policies have been updated




Google Play content policies have been updated. See "Content Policies" section of Google Play Developer Program Policies, which clarifies that "An app downloaded from Google Play may not modify, replace or update its own APK binary code using any method other than Google Play's update mechanism." Google Play is a trusted source for Android application downloads, and we are committed to providing a secure and consistent experience.

Tablet Optimization Tips in the Google Play Developer Console

Posted by Ellie Powers, Google Play team




Last week we updated our guidelines for making great tablet apps and added the ability to upload tablet screenshots that are shown preferentially in Google Play to users on those devices. Today we’re introducing a new Optimization Tips page in the Google Play Developer Console that lets you quickly see how your app is doing against basic guidelines for tablet app distribution and quality.



When you upload an app, the Developer Console now runs a series of checks to verify basic criteria from the Tablet App Quality Checklist and shows you any issues it finds in the Optimization Tips page.



style="border:1px solid #ddd;border-radius: 6px;"src="http://1.bp.blogspot.com/-w0gl8ttJFFo/UXgQDbepnuI/AAAAAAAACGE/nTanLxr3C7A/s1600/opt-tips-crop-720.png" />

If you’re developing for tablets, make sure to visit your Optimization Tips page to ensure that your app is delivering a great tablet experience. If there are any issues listed, we recommend addressing them in your app as soon as possible and uploading a new binary for distribution, if needed.



For ideas on how to design and build a great tablet app, including details on how to address issues listed in your Optimization Tips page, check out the Tablet App Quality Checklist. Remember that a great tablet experience goes well beyond these basic checks. Keep working to bring your tablet users the most polished UI and richest content possible.



Start Animation in Activity start

Last exercise show how to "Implement Animation while switching Activity, using overridePendingTransition()", after startActivity() or finish(). If you want run animation when your activity start, you can startAnimation() with your custom Animation in onCreate() or onResume() depends on what you expect.


Modify from last exercise.

Create /res/anim/rotate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator">
<rotate
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="50%"
android:pivotY="50%"
android:duration="1000"
android:startOffset="0"
android:repeatCount="1"
android:repeatMode="reverse" />
</set>


Modify activity_main.xml to add more view to animate.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
android:id="@+id/screen"
tools:context=".MainActivity" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:id="@+id/switchbutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Switch Activity2" />
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />
<ImageView
android:id="@+id/bigicon"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/ic_launcher" />

</LinearLayout>


MainActivity.java
package com.example.androidactivityanimation;

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.app.Activity;
import android.content.Intent;

public class MainActivity extends Activity {

LinearLayout screen;
Button switchButton;
ImageView bigIcon;
ImageView icon;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

screen = (LinearLayout)findViewById(R.id.screen);
bigIcon = (ImageView)findViewById(R.id.bigicon);
icon = (ImageView)findViewById(R.id.icon);

switchButton = (Button)findViewById(R.id.switchbutton);
switchButton.setOnClickListener(new OnClickListener(){

@Override
public void onClick(View arg0) {
Intent intent = new Intent();
intent.setClass(MainActivity.this, Activity2.class);
startActivity(intent);
overridePendingTransition(R.anim.right_in, R.anim.left_out);
}});

Animation animRightIn = AnimationUtils.loadAnimation(this, R.anim.right_in);
Animation animRotateIn_icon = AnimationUtils.loadAnimation(this, R.anim.rotate);

screen.startAnimation(animRightIn);
icon.startAnimation(animRotateIn_icon);
}

@Override
protected void onResume() {
super.onResume();
Animation animRotateIn_big = AnimationUtils.loadAnimation(this, R.anim.rotate);
bigIcon.startAnimation(animRotateIn_big);
}

}



download filesDownload the files.

Custom Animation while switching Activity, using overridePendingTransition()

Call overridePendingTransition(int enterAnim, int exitAnim) immediately after startActivity(Intent) or finish() to specify an explicit transition animation to perform next.


Create animation XMLs in /res/anim/ folder.

/res/anim/left_out.xml
<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator">
<translate
android:fromXDelta="0"
android:toXDelta="-100%p"
android:duration="500"/>
</set>


/res/anim/right_in.xml
<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator">
<translate
android:fromXDelta="100%p"
android:toXDelta="0"
android:duration="500"/>
</set>


/res/anim/top_out.xml
<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator">
<translate
android:fromYDelta="0"
android:toYDelta="-100%p"
android:duration="500"/>
</set>


/res/anim/bottom_in.xml
<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/linear_interpolator">
<translate
android:fromYDelta="100%p"
android:toYDelta="0"
android:duration="500"/>
</set>


MainActivity.java
package com.example.androidactivityanimation;

import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.app.Activity;
import android.content.Intent;

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Button switchButton = (Button)findViewById(R.id.switchbutton);
switchButton.setOnClickListener(new OnClickListener(){

@Override
public void onClick(View arg0) {
Intent intent = new Intent();
intent.setClass(MainActivity.this, Activity2.class);
startActivity(intent);
overridePendingTransition(R.anim.right_in, R.anim.left_out);
}});
}

}


/res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:id="@+id/switchbutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Switch Activity2" />


</LinearLayout>


Activity2.java
package com.example.androidactivityanimation;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class Activity2 extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout2);

Button backButton = (Button)findViewById(R.id.backbutton);
backButton.setOnClickListener(new OnClickListener(){

@Override
public void onClick(View v) {
finish();
overridePendingTransition(R.anim.bottom_in, R.anim.top_out);
}});
}

}


/res/layout/layout2.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".Activity2" >

<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/ic_launcher"/>
<Button
android:id="@+id/backbutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Back" />

</RelativeLayout>


AndroidManifest.xml have to be modified to add <activity> of Activity2.

download filesDownload the files.

Related:
- Start Animation in Activity start

implements OnBackStackChangedListener to display UP icon, automatically by getBackStackEntryCount()

Last exercise handle UP icon for Fragment navigation in hard code; call setDisplayHomeAsUpEnabled(true) when switch to MyDetailFragment, and call setDisplayHomeAsUpEnabled(false) when switch back to MyListFragment(). In case you have many Fragment Transaction, it's not a good approach to display the UP icon.

Alternatively, we can implements OnBackStackChangedListener, add it in FragmentManager by calling addOnBackStackChangedListener(), and override onBackStackChanged() method to check emptiness of back stack entry by calling getBackStackEntryCount(), then call setDisplayHomeAsUpEnabled() accordingly.

implements OnBackStackChangedListener to display UP icon, automatically by getBackStackEntryCount()


package com.example.androiddualmode;

import android.os.Bundle;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager.OnBackStackChangedListener;
import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity implements OnBackStackChangedListener{

// if run on phone, isSinglePane = true
// if run on tablet, isSinglePane = false
static boolean isSinglePane;

static String[] month ={
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"};

public static class MyListFragment extends ListFragment {

@Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);

ListAdapter myArrayAdapter =
new ArrayAdapter<String>(
getActivity(), android.R.layout.simple_list_item_1, month);
setListAdapter(myArrayAdapter);

}

@Override
public void onListItemClick(ListView l, View v, int position, long id) {

String clickedDetail = (String)l.getItemAtPosition(position);

if(isSinglePane == true){
/*
* The second fragment not yet loaded.
* Load MyDetailFragment by FragmentTransaction, and pass
* data from current fragment to second fragment via bundle.
*/
MyDetailFragment myDetailFragment = new MyDetailFragment();
Bundle bundle = new Bundle();
bundle.putString("KEY_DETAIL", clickedDetail);
myDetailFragment.setArguments(bundle);
FragmentTransaction fragmentTransaction =
getActivity().getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.phone_container, myDetailFragment);

/*
* Add this transaction to the back stack.
* This means that the transaction will be remembered after it is
* committed, and will reverse its operation when later popped off
* the stack.
*/
fragmentTransaction.addToBackStack(null);

fragmentTransaction.commit();

//getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);

}else{
/*
* Activity have two fragments. Pass data between fragments
* via reference to fragment
*/

//get reference to MyDetailFragment
MyDetailFragment myDetailFragment =
(MyDetailFragment)getFragmentManager().findFragmentById(R.id.detail_fragment);
myDetailFragment.updateDetail(clickedDetail);

}
}

}

public static class MyDetailFragment extends Fragment {

TextView textDetail;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View view = inflater.inflate(R.layout.layout_detailfragment, null);
textDetail = (TextView)view.findViewById(R.id.text_detail);

Bundle bundle = getArguments();
if(bundle != null){
String detail = bundle.getString("KEY_DETAIL", "no argument pass");
textDetail.setText(detail);
}

return view;
}

public void updateDetail(String detail) {
textDetail.setText(detail);
}

}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

View v = findViewById(R.id.phone_container);
if(v == null){
//it's run on tablet
isSinglePane = false;
/*
* MyListFragment and MyDetailFragment have been loaded in XML,
* no need load.
*/

}else{
//it's run on phone
//Load MyListFragment programmatically
isSinglePane = true;

if(savedInstanceState == null){
//if's the first time created
MyListFragment myListFragment = new MyListFragment();
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.phone_container, myListFragment);
fragmentTransaction.commit();
}
}

getFragmentManager().addOnBackStackChangedListener(this);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
getFragmentManager().popBackStack();
//getActionBar().setDisplayHomeAsUpEnabled(false);
return true;
}

return super.onOptionsItemSelected(item);
}

final static String KEY_DISPLAY_OPT = "KEY_Display_Option";

@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
outState.putInt(KEY_DISPLAY_OPT, getActionBar().getDisplayOptions());
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onRestoreInstanceState(savedInstanceState);
int savedDisplayOpt = savedInstanceState.getInt(KEY_DISPLAY_OPT);
if(savedDisplayOpt != 0){
getActionBar().setDisplayOptions(savedDisplayOpt);
}
}

@Override
public void onBackStackChanged() {
int backStackEntryCount = getFragmentManager().getBackStackEntryCount();
if(backStackEntryCount > 0){
getActionBar().setDisplayHomeAsUpEnabled(true);
}else{
getActionBar().setDisplayHomeAsUpEnabled(false);
}
}

}


download filesDownload the files.

Next:
- Implement animation in FragmentTransaction


Display UP icon on action bar and implement BACK navigation in Fragment Transaction

Follow the exercise "Allow navigate BACK through FragmentTransaction, by calling addToBackStack()", we are going to add a UP icon on non-home fragment, MyDetailFragment, to navigate BACK in the task history.

UP icon on action bar


  • To display/hide the UP icon, call getActionBar().setDisplayHomeAsUpEnabled(true/false) method.
  • Handle MenuItem of android.R.id.home in onOptionsItemSelected() to detect the UP icon clicked event.
  • To navigate back, call getFragmentManager().popBackStack().
  • Save and restore DisplayOptions of ActionBar in onSaveInstanceState() and onRestoreInstanceState(). Otherwise, the UP icon will disappear after orientation changed.

Modify MainActivity.java in "Allow navigate BACK through FragmentTransaction, by calling addToBackStack()".

package com.example.androiddualmode;

import android.os.Bundle;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity {

// if run on phone, isSinglePane = true
// if run on tablet, isSinglePane = false
static boolean isSinglePane;

static String[] month ={
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"};

public static class MyListFragment extends ListFragment {

@Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);

ListAdapter myArrayAdapter =
new ArrayAdapter<String>(
getActivity(), android.R.layout.simple_list_item_1, month);
setListAdapter(myArrayAdapter);

}

@Override
public void onListItemClick(ListView l, View v, int position, long id) {

String clickedDetail = (String)l.getItemAtPosition(position);

if(isSinglePane == true){
/*
* The second fragment not yet loaded.
* Load MyDetailFragment by FragmentTransaction, and pass
* data from current fragment to second fragment via bundle.
*/
MyDetailFragment myDetailFragment = new MyDetailFragment();
Bundle bundle = new Bundle();
bundle.putString("KEY_DETAIL", clickedDetail);
myDetailFragment.setArguments(bundle);
FragmentTransaction fragmentTransaction =
getActivity().getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.phone_container, myDetailFragment);

/*
* Add this transaction to the back stack.
* This means that the transaction will be remembered after it is
* committed, and will reverse its operation when later popped off
* the stack.
*/
fragmentTransaction.addToBackStack(null);

fragmentTransaction.commit();

getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);

}else{
/*
* Activity have two fragments. Pass data between fragments
* via reference to fragment
*/

//get reference to MyDetailFragment
MyDetailFragment myDetailFragment =
(MyDetailFragment)getFragmentManager().findFragmentById(R.id.detail_fragment);
myDetailFragment.updateDetail(clickedDetail);

}
}

}

public static class MyDetailFragment extends Fragment {

TextView textDetail;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View view = inflater.inflate(R.layout.layout_detailfragment, null);
textDetail = (TextView)view.findViewById(R.id.text_detail);

Bundle bundle = getArguments();
if(bundle != null){
String detail = bundle.getString("KEY_DETAIL", "no argument pass");
textDetail.setText(detail);
}

return view;
}

public void updateDetail(String detail) {
textDetail.setText(detail);
}

}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

View v = findViewById(R.id.phone_container);
if(v == null){
//it's run on tablet
isSinglePane = false;
/*
* MyListFragment and MyDetailFragment have been loaded in XML,
* no need load.
*/

}else{
//it's run on phone
//Load MyListFragment programmatically
isSinglePane = true;

if(savedInstanceState == null){
//if's the first time created
MyListFragment myListFragment = new MyListFragment();
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.phone_container, myListFragment);
fragmentTransaction.commit();
}
}
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
getFragmentManager().popBackStack();
getActionBar().setDisplayHomeAsUpEnabled(false);
return true;
}

return super.onOptionsItemSelected(item);
}

final static String KEY_DISPLAY_OPT = "KEY_Display_Option";

@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
outState.putInt(KEY_DISPLAY_OPT, getActionBar().getDisplayOptions());
}


@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onRestoreInstanceState(savedInstanceState);
int savedDisplayOpt = savedInstanceState.getInt(KEY_DISPLAY_OPT);
if(savedDisplayOpt != 0){
getActionBar().setDisplayOptions(savedDisplayOpt);
}
}

}


download filesDownload the files.

Next:
- implements OnBackStackChangedListener to display UP icon, automatically by getBackStackEntryCount()


Replace Fragment

In this exercise, we are doing Fragment replacement by Java code. There are three button on the main layout to select fragment to display, a container of FrameLayout to load fragments dynamically.

Replace Fragment dynamically


Main layout, activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<Button
android:id="@+id/button1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Fragment 1"/>
<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Fragment 2"/>
<Button
android:id="@+id/button3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Fragment 3"/>

</LinearLayout>
<FrameLayout
android:id="@+id/maincontainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

</FrameLayout>

</LinearLayout>


The layouts of the fragments.

fragmentlayout_01.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/container1" >

<TextView
android:id="@+id/textmsg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fragment 1" />
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/ic_launcher" />

</LinearLayout>


fragmentlayout_02.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/container2" >

<TextView
android:id="@+id/textmsg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fragment 2" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_launcher" />

</LinearLayout>


fragmentlayout_03.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/container3" >

<TextView
android:id="@+id/textmsg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fragment 3" />
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="center"
android:src="@drawable/ic_launcher" />

</LinearLayout>


Main code, MainActivity.java
package com.example.androidreplacefragment;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.TextView;

public class MainActivity extends FragmentActivity {

public static class MyFragment1 extends Fragment {

TextView textMsg;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentlayout_01, null);
textMsg = (TextView)view.findViewById(R.id.textmsg);

Bundle bundle = getArguments();
if(bundle != null){
String msg = bundle.getString(KEY_MSG_1);
if(msg != null){
textMsg.setText(msg);
}
}

return view;
}

public void setMsg(String msg){
textMsg.setText(msg);
}

}

public static class MyFragment2 extends Fragment {

TextView textMsg;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentlayout_02, null);
textMsg = (TextView)view.findViewById(R.id.textmsg);

Bundle bundle = getArguments();
if(bundle != null){
String msg = bundle.getString(KEY_MSG_2);
if(msg != null){
textMsg.setText(msg);
}
}

return view;
}

public void setMsg(String msg){
textMsg.setText(msg);
}

}

public static class MyFragment3 extends Fragment {

TextView textMsg;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragmentlayout_03, null);
textMsg = (TextView)view.findViewById(R.id.textmsg);

Bundle bundle = getArguments();
if(bundle != null){
String msg = bundle.getString(KEY_MSG_3);
if(msg != null){
textMsg.setText(msg);
}
}

return view;
}

public void setMsg(String msg){
textMsg.setText(msg);
}

}

FrameLayout container;
FragmentManager myFragmentManager;
MyFragment1 myFragment1;
MyFragment2 myFragment2;
MyFragment3 myFragment3;
final static String TAG_1 = "FRAGMENT_1";
final static String TAG_2 = "FRAGMENT_2";
final static String TAG_3 = "FRAGMENT_3";
final static String KEY_MSG_1 = "FRAGMENT1_MSG";
final static String KEY_MSG_2 = "FRAGMENT2_MSG";
final static String KEY_MSG_3 = "FRAGMENT3_MSG";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
container = (FrameLayout)findViewById(R.id.maincontainer);

Button button1 = (Button)findViewById(R.id.button1);
Button button2 = (Button)findViewById(R.id.button2);
Button button3 = (Button)findViewById(R.id.button3);

button1.setOnClickListener(new OnClickListener(){

@Override
public void onClick(View arg0) {

MyFragment1 fragment = (MyFragment1)myFragmentManager.findFragmentByTag(TAG_1);

if (fragment == null) {

Bundle bundle = new Bundle();
bundle.putString(KEY_MSG_1, "Replace MyFragment1");
myFragment1.setArguments(bundle);

FragmentTransaction fragmentTransaction = myFragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.maincontainer, myFragment1, TAG_1);
fragmentTransaction.commit();

}else{

fragment.setMsg("MyFragment1 already loaded");
}
}});

button2.setOnClickListener(new OnClickListener(){

@Override
public void onClick(View arg0) {

MyFragment2 fragment = (MyFragment2)myFragmentManager.findFragmentByTag(TAG_2);

if (fragment == null) {

Bundle bundle = new Bundle();
bundle.putString(KEY_MSG_2, "Replace MyFragment2");
myFragment2.setArguments(bundle);

FragmentTransaction fragmentTransaction = myFragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.maincontainer, myFragment2, TAG_2);
fragmentTransaction.commit();

}else{
fragment.setMsg("MyFragment2 already loaded");
}
}});

button3.setOnClickListener(new OnClickListener(){

@Override
public void onClick(View arg0) {

MyFragment3 fragment = (MyFragment3)myFragmentManager.findFragmentByTag(TAG_3);

if (fragment == null) {

Bundle bundle = new Bundle();
bundle.putString(KEY_MSG_3, "Replace MyFragment3");
myFragment3.setArguments(bundle);

FragmentTransaction fragmentTransaction = myFragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.maincontainer, myFragment3, TAG_3);
fragmentTransaction.commit();

}else{
fragment.setMsg("MyFragment3 already loaded");
}
}});

myFragmentManager = getSupportFragmentManager();
myFragment1 = new MyFragment1();
myFragment2 = new MyFragment2();
myFragment3 = new MyFragment3();

if(savedInstanceState == null){
//if's the first time created

FragmentTransaction fragmentTransaction = myFragmentManager.beginTransaction();
fragmentTransaction.add(R.id.maincontainer, myFragment1, TAG_1);
fragmentTransaction.commit();
}
}

}


download filesDownload the files.



Related:
- Dual display mode using Fragment step-by-step - one-pane with fragment transaction for phone, two-pane for tablet

Allow navigate BACK through FragmentTransaction, by calling addToBackStack()

If you try last exercise "Handle onListItemClick() of ListFragment, to pass data between fragment" on phone FragmentTransaction, when you BACK in MyDetailFragment, it will exit the application, not back to MyListFragment as common expected. To allow the user to navigate backward through the fragment transactions, you must call addToBackStack() before you commit the FragmentTransaction.


package com.example.androiddualmode;

import android.os.Bundle;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity {

// if run on phone, isSinglePane = true
// if run on tablet, isSinglePane = false
static boolean isSinglePane;

static String[] month ={
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"};

public static class MyListFragment extends ListFragment {

@Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);

ListAdapter myArrayAdapter =
new ArrayAdapter<String>(
getActivity(), android.R.layout.simple_list_item_1, month);
setListAdapter(myArrayAdapter);

}

@Override
public void onListItemClick(ListView l, View v, int position, long id) {

String clickedDetail = (String)l.getItemAtPosition(position);

if(isSinglePane == true){
/*
* The second fragment not yet loaded.
* Load MyDetailFragment by FragmentTransaction, and pass
* data from current fragment to second fragment via bundle.
*/
MyDetailFragment myDetailFragment = new MyDetailFragment();
Bundle bundle = new Bundle();
bundle.putString("KEY_DETAIL", clickedDetail);
myDetailFragment.setArguments(bundle);
FragmentTransaction fragmentTransaction =
getActivity().getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.phone_container, myDetailFragment);

/*
* Add this transaction to the back stack.
* This means that the transaction will be remembered after it is
* committed, and will reverse its operation when later popped off
* the stack.
*/
fragmentTransaction.addToBackStack(null);

fragmentTransaction.commit();

}else{
/*
* Activity have two fragments. Pass data between fragments
* via reference to fragment
*/

//get reference to MyDetailFragment
MyDetailFragment myDetailFragment =
(MyDetailFragment)getFragmentManager().findFragmentById(R.id.detail_fragment);
myDetailFragment.updateDetail(clickedDetail);

}
}

}

public static class MyDetailFragment extends Fragment {

TextView textDetail;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View view = inflater.inflate(R.layout.layout_detailfragment, null);
textDetail = (TextView)view.findViewById(R.id.text_detail);

Bundle bundle = getArguments();
if(bundle != null){
String detail = bundle.getString("KEY_DETAIL", "no argument pass");
textDetail.setText(detail);
}

return view;
}

public void updateDetail(String detail) {
textDetail.setText(detail);
}

}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

View v = findViewById(R.id.phone_container);
if(v == null){
//it's run on tablet
isSinglePane = false;
/*
* MyListFragment and MyDetailFragment have been loaded in XML,
* no need load.
*/

}else{
//it's run on phone
//Load MyListFragment programmatically
isSinglePane = true;

if(savedInstanceState == null){
//if's the first time created
MyListFragment myListFragment = new MyListFragment();
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.phone_container, myListFragment);
fragmentTransaction.commit();
}
}
}

}


Next:
- Display UP icon on action bar and implement BACK navigation in Fragment Transaction


Handle onListItemClick() of ListFragment, to pass data between fragment

Last exercise "Handle different layout for phone and tablet" demonstrate how to display difference layouts on phone and tablet. Here we are going to handle user click on list item and display the item on the detail fragment.

There are two case in our approach:

- In phone mode with single pane, the target fragment (MyDetailFragment) is not yet displayed. We have to replace the fragment in container with FragmentTransaction, and pass the data via Bundle by calling setArguments(), and retrieve the data in target fragment by calling getArguments() method.

Replace fragment in phone mode


- In tablet mode with two panes, the detail fragment is already displayed. We can call a method in target fragment via FragmentManager and find the target fragment by calling findFragmentById() method.

Pass data between fragment in tablet mode


Modify /res/layout/layout_detailfragment.xml to add a TextView to display the clicked item.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:id="@+id/title_detailfragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Detail Fragment"/>
<TextView
android:id="@+id/text_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

</LinearLayout>


MainActivity.java
package com.example.androiddualmode;

import android.os.Bundle;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends Activity {

// if run on phone, isSinglePane = true
// if run on tablet, isSinglePane = false
static boolean isSinglePane;

static String[] month ={
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"};

public static class MyListFragment extends ListFragment {

@Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);

ListAdapter myArrayAdapter =
new ArrayAdapter<String>(
getActivity(), android.R.layout.simple_list_item_1, month);
setListAdapter(myArrayAdapter);

}

@Override
public void onListItemClick(ListView l, View v, int position, long id) {

String clickedDetail = (String)l.getItemAtPosition(position);

if(isSinglePane == true){
/*
* The second fragment not yet loaded.
* Load MyDetailFragment by FragmentTransaction, and pass
* data from current fragment to second fragment via bundle.
*/
MyDetailFragment myDetailFragment = new MyDetailFragment();
Bundle bundle = new Bundle();
bundle.putString("KEY_DETAIL", clickedDetail);
myDetailFragment.setArguments(bundle);
FragmentTransaction fragmentTransaction =
getActivity().getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.phone_container, myDetailFragment);
fragmentTransaction.commit();

}else{
/*
* Activity have two fragments. Pass data between fragments
* via reference to fragment
*/

//get reference to MyDetailFragment
MyDetailFragment myDetailFragment =
(MyDetailFragment)getFragmentManager().findFragmentById(R.id.detail_fragment);
myDetailFragment.updateDetail(clickedDetail);

}
}

}

public static class MyDetailFragment extends Fragment {

TextView textDetail;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View view = inflater.inflate(R.layout.layout_detailfragment, null);
textDetail = (TextView)view.findViewById(R.id.text_detail);

Bundle bundle = getArguments();
if(bundle != null){
String detail = bundle.getString("KEY_DETAIL", "no argument pass");
textDetail.setText(detail);
}

return view;
}

public void updateDetail(String detail) {
textDetail.setText(detail);
}

}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

View v = findViewById(R.id.phone_container);
if(v == null){
//it's run on tablet
isSinglePane = false;
/*
* MyListFragment and MyDetailFragment have been loaded in XML,
* no need load.
*/

}else{
//it's run on phone
//Load MyListFragment programmatically
isSinglePane = true;

if(savedInstanceState == null){
//if's the first time created
MyListFragment myListFragment = new MyListFragment();
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.phone_container, myListFragment);
fragmentTransaction.commit();
}
}
}

}




download filesDownload the files.

Next:
- Allow navigate BACK through FragmentTransaction, by calling addToBackStack()


Programming the Mobile Web, second edition


With the second edition of this popular book, you’ll learn how to build HTML5 and CSS3-based apps that access geolocation, accelerometer, multi-touch screens, offline storage, and other features in today’s smartphones, tablets, and feature phones. The market for mobile apps continues to evolve at a breakneck pace, and this book is the most complete reference available for the mobile web.
Author and mobile development expert Maximiliano Firtman shows you how to develop a standard app core that you can extend to work with specific devices. This updated edition covers many recent advances in mobile development, including responsive web design techniques, offline storage, mobile design patterns, and new mobile browsers, platforms, and hardware APIs.
  • Learn the particulars and pitfalls of building mobile websites and apps with HTML5, CSS, JavaScript and responsive techniques
  • Create effective user interfaces for touch devices and different resolution displays
  • Understand variations among iOS, Android, Windows Phone, BlackBerry, Firefox OS, and other mobile platforms
  • Bypass the browser to create native web apps, ebooks, and PhoneGap applications
  • Build apps for browsers and online retailers such as the App Store, Google Play Store, Windows Store, and App World

Handle different layout for phone and tablet, load fragment in Java code vs XML

Last exercise demonstrate to create /res/layout/activity_main.xml for phone, and /res/layout-sw600dp/activity_main.xml for tablet. We are going to handle the layout separately.

In single pane mode run on phone, We have one pane displayed only. We have to switch fragment using Java code programm programmatically; if define in XML we cannot switch fragment later. We have a FrameLayout with id "phone_container" in layout xml. It will be used to determine if it's run on phone or tablet. We will load MyListFragment in onCreate() programmatically.

/res/layout/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Normal" />

<FrameLayout
android:id="@+id/phone_container"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</FrameLayout>

</LinearLayout>

Run on phone with a ListFragment


In two pane mode, run on tablet. There are two fragments in layout. The fragments are loaded in XML.

/res/layout-sw600dp/activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="sw600dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<fragment
android:name="com.example.androiddualmode.MainActivity$MyListFragment"
android:id="@+id/list_fragment"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1" />
<fragment
android:name="com.example.androiddualmode.MainActivity$MyDetailFragment"
android:id="@+id/detail_fragment"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="2" />

</LinearLayout>

</LinearLayout>

Run on tablet with two panes


Create /res/layout/layout_detailfragment.xml, it's a simple layout in the MyDetailFragment.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:id="@+id/title_detailfragment"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Detail Fragment"/>

</LinearLayout>


Modify MainActivity.java. Implement inner class of MyListFragment and MyDetailFragment.
package com.example.androiddualmode;

import android.os.Bundle;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListAdapter;

public class MainActivity extends Activity {

// if run on phone, isSinglePane = true
// if run on tablet, isSinglePane = false
boolean isSinglePane;

static String[] month ={
"January", "February", "March", "April",
"May", "June", "July", "August",
"September", "October", "November", "December"};

public static class MyListFragment extends ListFragment {

@Override
public void onActivityCreated(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onActivityCreated(savedInstanceState);

ListAdapter myArrayAdapter =
new ArrayAdapter<String>(
getActivity(), android.R.layout.simple_list_item_1, month);
setListAdapter(myArrayAdapter);

}

}

public static class MyDetailFragment extends Fragment {

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View view = inflater.inflate(R.layout.layout_detailfragment, null);
return view;
}

}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

View v = findViewById(R.id.phone_container);
if(v == null){
//it's run on tablet
isSinglePane = false;
/*
* MyListFragment and MyDetailFragment have been loaded in XML,
* no need load.
*/

}else{
//it's run on phone
//Load MyListFragment programmatically
isSinglePane = true;

if(savedInstanceState == null){
//if's the first time created
MyListFragment myListFragment = new MyListFragment();
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.phone_container, myListFragment);
fragmentTransaction.commit();
}
}
}

}


download filesDownload the files.

Next:
- Handle onListItemClick() of ListFragment, to pass data between fragment