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.
One of the best simple tutorial available on the net. Nice Job. continue posting. :)
ReplyDelete@Movies4free, I agree. This tutorial is great, super simple and easy to understand. Thanks!
ReplyDeleteI agree..
DeleteGood one to startwith.
ReplyDeleteThis comment has been removed by the author.
ReplyDelete