0

So, While learning the basics of android, this word Fragment came up at many places like Navigation Drawers , ViewPager , PreferencesActivity , creating a dynamic view , etc . So i started reading about it and turns out this is a very important part of the Android Platform .It enhances the user Experience by allowing apps spread their data across screens of different sizes in a better manner.

For example Apps like Gmail will show their data across multiple "pages" (check the image below for a better idea) on small screen devices, and will show All of their data in a single page in larger devices like Tablets.


So the question that usually arisis is "How fragments does that?" and "How does it manages its magical clicks , which changes layout completely when in a small screen device , but simply changes its data , when in a large screen device?" Well This is quite a difficult problem when we think in the aspects of Activity layouts.

So after a little more searches and reading, i was able to successfully learn more about the theory behind fragments,their LifeCycles and static fragments ( Fragment Documentation ) .

What i now wished was to implement some real life application of fragments, like gmail, which comprises of the concept of adding fragments programmically . So I devised a small task which helped me in learning the concept of dynamic fragments and fragment-fragment / fragment-activity communication.

Task:

  • There will be a basic application with 1 MainActivity having LinearLayout as its root viewGroup.
  • 2 Fragments: FragmentA (or Frag 1 or Pink background fragment) and FragmentB (or frag2/ BlueFragment) will be added to the MainActivity during first run.
  • Both Fragment should be able to manipulate the other fragment's visibility for the rest of time, therefore each fragment will have 2 buttons open Other fragment and Close other fragment.
  • On clicking open frag1 in fragment 2, fragment 1 should be added to screen or do nothing, if already present . Similarly pressing close frag1 in fragment 2 should be able to remove fragment 1, if fragment1 is already present or do nothing .

target image samples:

  • An activity showing 2 fragments, the pink fragment and the blue fragment(ignore there sizes, was testing linearlayout#weight):
    sample3

  • Same activity showing only fragment1 now , because user pressed close fragment 2 inside the pink fragment . Thus layout gets dynamically changed :
    sample

(Same could be applied for the second fragment too)

Achievement : well, after doing this i was able to get a better view of Inter-Fragment communication and i think i now know how those "magical clicks" in Gmail changes data/Layouts in a completely different fragment .

I think it will be unwise to present a set of my all possible failure attempts here , So am just mentioning some of the other problems I faced while solving this task :

Problems:
1. Fragments are unable to communicate with each other(a button pressed in fragment 1 is unable to close fragment 2)
2. Fragments gets re-added to the screen multiple times on rotation.
3. Saving display contents: when App is launched, the fragment 1 is being shown by default. During the session,this layout can change to 2 fragments being shown or just the fragment'2' being shown,according to user's input. However , if the user rotates his phone, the content present on the screen is not saved and the activity starts displaying fragment 1 .

ansh sachdeva
  • 1,220
  • 1
  • 15
  • 32

1 Answers1

1

step 0 :creating a fragment, other files
create fragment.xml, fragment.java extending fragment and inflate layout in onCreateView().(for both fragments)

public class Frag_b extends Fragment {

    public Frag_b() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_frag_b, container, false);
    }

}

step (1) Dynamic Fragments:
As we know when an app launches, the androidOS first calls the activity to show up(the one having "android.intent.action.MAIN" permission ). So to display a fragment in runtime we use a class called FragmentManager(Read more about its features here.So, to attach a fragment:
1 - create global objects of both the fragments and fragment manager in main activity.
2 - create global fragment tags : they are very important for restorability of same fragment objects.They will be used as tags to access/ identify strings.
3 - addition via fragment manager: So here comes the first use-case of fragment manager : to add fragments dynamically(at run time). here is the code for it:

public class MainActivity extends AppCompatActivity implements FragAHandler,FragBHandler {
    LinearLayout layRoot;

    FragmentManager manager=getSupportFragmentManager();

    public static  final String FRAG_A_TAG="FRAG_A";
    public static  final String FRAG_B_TAG="FRAG_B";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        layRoot=findViewById(R.id.layout_root);
        manager.beginTransaction().add(R.id.layout_root,new Frag_a(),FRAG_A_TAG).commit();
        manager.beginTransaction().add(R.id.layout_root,new Frag_b(),FRAG_B_TAG).commit();

    }

}

step 2 Fragment Communication:
As indicated here, fragment communication is a 4 layered process:
* An interface defines the functions(the task that fragment wants to perform).
* The activity implements them.
* The fragment gets an instance of this interface(in the form of activity's context).
* Fragment uses this instance according to its need .
{in case of fragment-fragment communication , 2 more small steps are added to it}:
* the fragment sends a signal to activity.
* the activity then handles the signal and sends it to other fragment.

Thus if a fragment FragA wants the main activity to show notification on a button click(present inside fragment ):

  • create an interface FragAcall having function void showNotif().
  • Implement it in MainActivity.(and define whatever you wish that your activity should perform on receiving click from fragment A .i.e, showing a notification).
  • In fragA create an object of interface FragAcall (FragAcall callObj).
  • In fragA, override a method named onAttach(context).cast this context to FragAcall' Object.
  • Inside your button's on click listener, call callObj.showNotiff().

similarly for a 2 way fragment-fragment communication, I implemented the code using these steps: * created interfaces FragAHandler and FragBHandler:

public interface FragAHandler {
    void addFrag1();
    void closeFrag1();
    }
public interface FragBHandler {
    void addFrag2();
    void closeFrag2();
    }
  • implement these in main activity:

    public class MainActivity extends AppCompatActivity implements FragAHandler,FragBHandler
    {
    
    LinearLayout layRoot;
    FragmentManager manager=getSupportFragmentManager();
    public static final String FRAG_A_TAG = "FRAG_A";
    public static final String FRAG_B_TAG = "FRAG_B";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        layRoot = findViewById(R.id.layout_root);
        manager.beginTransaction().add(R.id.layout_root, new Frag_a(), FRAG_A_TAG).commit();
    }
    
    @Override
    public void addFrag1() {
        if (manager.findFragmentByTag(FRAG_A_TAG) == null) {
            manager.beginTransaction().add(R.id.layout_root, new Frag_a(), FRAG_A_TAG).commit();
        } else {
            Toast.makeText(MainActivity.this, "frag1 Already present", Toast.LENGTH_SHORT);
        }
    }
    
    @Override
    public void closeFrag1() {
        Frag_a fragA = (Frag_a) manager.findFragmentByTag(FRAG_A_TAG);
        if (fragA != null) {
            manager.beginTransaction().remove(fragA).commit();
        } else {
            Toast.makeText(MainActivity.this, "frag1 Already not there", Toast.LENGTH_SHORT);
        }
    
    }
    
    @Override
    public void addFrag2() {
    
        if (manager.findFragmentByTag(FRAG_B_TAG) == null) {
            manager.beginTransaction().add(R.id.layout_root, new Frag_b(), FRAG_B_TAG).commit();
        } else {
            Toast.makeText(MainActivity.this, "frag2 Already present", Toast.LENGTH_SHORT);
        }
    }
    
    @Override
    public void closeFrag2() {
        Frag_b frag_b = (Frag_b) manager.findFragmentByTag(FRAG_B_TAG);
    
        if (frag_b != null) {
            manager.beginTransaction().remove(frag_b).commit();
        } else {
            Toast.makeText(MainActivity.this, "frag2 Already not there", Toast.LENGTH_SHORT);
    
        }
    
    }
    }
    

In this code manager.findFragByTag() is being used to check weather a fragment is already being displayed or not,since we only want to open one single instance of the other fragment. for its detailed use, see this

  • Inside fragments, attach the mainActivity's class context to your handler obj and use it inside buttons:

    public class Frag_a extends Fragment {
    
        FragBHandler fragBHandler;
    
        public Frag_a() {
            // Required empty public constructor
        }
    
        @Override
        public void onAttach(Context context) {
            // context is the activity's context.
            super.onAttach(context);
    
            try {
                // This makes sure that the container activity has implemented
                // the callback interface. If not, it throws an exception
                fragBHandler = (FragBHandler) context;
            } catch (ClassCastException e) {
                e.printStackTrace();
                Log.e("", "onAttach: class has not implemented fragAhandler");
            }
        }
        // THIS SHOULD NEVER BE AN APPROCH.
        // public Frag_a(FragBHandler fragBHandler) {
        // this.fragBHandler = fragBHandler;
        // }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            // Inflate the layout for this fragment
            View v = inflater.inflate(R.layout.fragment_frag_a, container, false);
            v.findViewById(R.id.bt_open_frag2).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    openFrag2();
                }
            });
            v.findViewById(R.id.bt_close_frag2).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    closeFrag2();
                }
            });
    
            return v;
        }
    
        public void closeFrag2() {
            fragBHandler.closeFrag2();
    
        }
    
        public void openFrag2() {
            fragBHandler.addFrag2();
    
        }
    
    }
    

VOILA ,BOTH FRAGMENTS CAN LITERALLY TALK NOW!
**Part 3: Fragment Duplication **
this was one of the most difficult task.up till now, my aim to create an app with 2 fragments communicating was complete .But when i rotated my phone, there was a major fail: whatever that was present on the screen was being created multiple times!

I found the solution by doing the following things:
- Using one common object for both frag1 and 2 that are created during onCreate(), and that too by checking if it is already present in the back-stack, then using that object only to add again.
- Adding the initial fragment also after checking the back-stack.

So my final for mainActivity code becomes:

    public class MainActivity extends AppCompatActivity implements FragAHandler, FragBHandler {
        LinearLayout layRoot;
        FragmentManager manager = getSupportFragmentManager();


        Frag_a fragmentObjA;
        Frag_b fragmentObjB;
        public static final String FRAG_A_TAG = "FRAG_A";
        public static final String FRAG_B_TAG = "FRAG_B";


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

            initialise();

            if (manager.findFragmentByTag(FRAG_A_TAG) == null && manager.findFragmentByTag(FRAG_B_TAG) == null) {
                //both are imp because of testcase not giving exact answer:
                // >>>open app: shows frag1>'press open frag2'>>>frg2 opens >'close frag 1' >>> rotate.
                //Expected output >> "fragment 2(already present object in backstack and on screen) to remain on screen
                //output recieved : both frag 1 and frg2 showing
                manager.beginTransaction().add(R.id.layout_root, fragmentObjA, FRAG_A_TAG)
                        .setTransitionStyle(FragmentTransaction.TRANSIT_ENTER_MASK)
                        .commit();
            } else {
                Toast.makeText(MainActivity.this, "frag1 Already present or fragment 2 present", Toast.LENGTH_SHORT).show();
            }

        }

        private void initialise() {
            layRoot = findViewById(R.id.layout_root);

            if(manager.findFragmentByTag(FRAG_A_TAG)!=null){
                fragmentObjA= (Frag_a) manager.findFragmentByTag(FRAG_A_TAG);
                Log.e(">>", "initialise: frgement A object recieved from the frag manager is used" );

            }
            else{
                Log.e(">>", "initialise:new frag a object created" );
                fragmentObjA=new Frag_a();

            }
            if(manager.findFragmentByTag(FRAG_B_TAG)!=null){
                fragmentObjB= (Frag_b) manager.findFragmentByTag(FRAG_B_TAG);

                Log.e(">>", "initialise: frgement B object recieved from the frag manager is used" );
            }
            else{
                Log.e(">>", "initialise:new frag object created" );
                fragmentObjB=new Frag_b();
            }
        }


        //-----------handler methods-------------------
        @Override
        public void addFrag1() {
            if (manager.findFragmentByTag(FRAG_A_TAG) == null) {
                manager.beginTransaction().add(R.id.layout_root, fragmentObjA, FRAG_A_TAG)
                        .setTransitionStyle(FragmentTransaction.TRANSIT_ENTER_MASK)
                        .commit();
            } else {
                Toast.makeText(MainActivity.this, "frag1 Already present", Toast.LENGTH_SHORT).show();
            }
        }

        @Override
        public void closeFrag1() {
            Frag_a fragA = (Frag_a) manager.findFragmentByTag(FRAG_A_TAG);
            if (fragA != null) {
                manager.beginTransaction().remove(fragA)
                        .setTransitionStyle(FragmentTransaction.TRANSIT_EXIT_MASK)
                        .commit();
            } else {
                Toast.makeText(MainActivity.this, "frag1 Already not there", Toast.LENGTH_SHORT).show();

            }

        }

        @Override
        public void addFrag2() {

            if (manager.findFragmentByTag(FRAG_B_TAG) == null) {
                manager.beginTransaction().add(R.id.layout_root, fragmentObjB, FRAG_B_TAG)
                        .setTransitionStyle(FragmentTransaction.TRANSIT_ENTER_MASK)
                        .commit();
            } else {
                Toast.makeText(MainActivity.this, "frag2 Already present", Toast.LENGTH_SHORT).show();
            }


        }

        @Override
        public void closeFrag2() {
            Frag_b frag_b = (Frag_b) manager.findFragmentByTag(FRAG_B_TAG);

            if (frag_b != null) {
                manager.beginTransaction().remove(frag_b)
                        .setTransitionStyle(FragmentTransaction.TRANSIT_EXIT_MASK)
                        .commit();
            } else {
                Toast.makeText(MainActivity.this, "frag2 Already not there", Toast.LENGTH_SHORT).show();

            }

        }

    }
ansh sachdeva
  • 1,220
  • 1
  • 15
  • 32