Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android managing fragments from activity elegantly

Description of what I'm trying to accomplish: I have an app that uses a FragmentActivity with a LinearLayout as a container for the fragments. I click different buttons on the FragmentActivity UI and add and remove Fragments to the container in the FragmentActivity. In addition to clicking buttons on the FragmentActivity UI, each Fragment also has buttons that can be clicked which will remove the current fragment and add a different fragment in its place.

The Android way of doing things as I understand it: I have been reading up on how to do this and as I understand it, the 'proper' way of doing things is to use the FragmentActivity as sort of a relay station and have each fragment do callbacks to the FragmentActivity to communicate events and deal with them.

Scenario: So let's say that the FragmentActivity is displaying Fragment A and when the user clicks a button in FragmentA I want to stop showing FragmentA and start showing FragmentB. To do this I have created an interface in FragmentA called AListener. In the onAttach() method of FragmentA I use the suggested method of checking that the FragmentActivity implements AListener. When the button in FragmentA is clicked I use one of the callback methods from AListener to communicate the click event to the FragmentActivity. In the FragmentActivity I create an instance of FragmentB and add it to the container in FragmentActivity. Then if some event happens in FragmentB I use the same scheme to communicate the event to the FragmentActivity and do something interesting.

So what's the problem? For my application I have found this scheme of having Fragments call back to the FragmentActivity and then having the FragmentActivity create a new fragment or call forward to and existing fragment very cumbersome. I have many fragments that need to be displayed by the FragmentActivity and therefore I am implementing an interface for every type of fragment that needs to be displayed (Each fragment is different so they each have their own interface). This causes clashes when I have two interfaces that have the same method signatures and I'm forced to rename one of the methods.

For instance, if I want to attach a listener to a fragment using the onAttach() method of the fragment, then my FragmentActivity must implement the interface. I have found several instances where I have callback methods that have the same name (or I'm forced to name them something similar but different because of a namespace collision). One solution to this would be to use an anonymous classes as callbacks instead of having the FragmentActivity implement the interface. This seems to work well enough, but goes against what the Android documentation says about using the onAttach() method to set the listener.

Are there any elegant ways to approach this problem? It seems to me the tradeoff is that you either force the FragmentActivity to implement an interface for each Fragment that you want to display in it and have the fun problem of watching out for method signature collisions, or you go against the Android documentation and use Anonymous classes to handle the callbacks (not sure of the implications of this).

I am fairly new to Java and feel like I could be missing a concept here that would solve my problem. Can anyone set me straight on how to solve this problem elegantly?

like image 831
neonDion Avatar asked Nov 19 '13 19:11

neonDion


People also ask

Can we navigate from activity to fragment?

If you want to go back from Activity to Fragment. This is very simple just override onBackPressed() in your activity and call onBackPressed where you want.

Why are fragments better than activities?

Activities are an ideal place to put global elements around your app's user interface, such as a navigation drawer. Conversely, fragments are better suited to define and manage the UI of a single screen or portion of a screen. Consider an app that responds to various screen sizes.

Does popBackStack destroy fragment?

FragmentManager popBackStack doesn't remove fragment.


1 Answers

I completely understand your problem since i was dealing it for a long time. Here is the solution i came up right now! It may need some modification based on your need but i it works well.

first of all to to make communicating of event easier in your app use an EventBus! here is the most famous one https://goo.gl/nAEW6 event bus allows you to send event from anywhere to anywhere without need to worry about implementing interfaces, broadcast receivers, threading, etc.

Then add FragmentOrganizer to your app. It's a base class for all of your Fragment Organizers. basically you need one for each activity. Here is the code

public abstract class FragmentOrganizer {

protected FragmentManager fragmentManager;

public FragmentOrganizer(FragmentManager fragmentManager) {
    this.fragmentManager = fragmentManager;
    openFragment(getInitialFragment());
    EventBus.getDefault().register(this);
}

protected abstract Fragment getInitialFragment();
protected abstract void onEvent(Object event);
public abstract boolean handleBackNavigation();

public void freeUpResources(){
    EventBus.getDefault().unregister(this);
}


protected Fragment getOpenFragment(){
    String tag = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() -1).getName();
    return fragmentManager.findFragmentByTag(tag);
}


protected boolean isFragmentOpen(Fragment fragment){
    return isFragmentOpen(fragment, true);
}

protected boolean isFragmentOpen(Fragment fragment, boolean useArgs){
    String fragmentTag = createFragmentTag(fragment, useArgs);

    if (fragmentManager.getBackStackEntryCount() != 0) {
        String name = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1).getName();

        if(!useArgs)
            name = name.substring(0, name.indexOf("-"));

            return name.equals(fragmentTag);
    }

    return false;
}


private String createFragmentTag(Fragment fragment, boolean addArgs) {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(fragment.getClass().getSimpleName());
    if(addArgs) {
        stringBuilder.append("-");
        if (fragment.getArguments() != null)
            stringBuilder.append(fragment.getArguments().toString());
    }
    return stringBuilder.toString();
}

public void openFragment(Fragment fragment) {
    if(isFragmentOpen(fragment))
        return;

    String fragmentTag = createFragmentTag(fragment, true);


    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.replace(R.id.activity_main_fragment_container, fragment, fragmentTag);
    transaction.addToBackStack(fragmentTag).commit();
}


}

Now you need to create your fragment organizer that inherit from FragmentOrganizer and implements 3 required methods. here the sample

public class MainFragmentOrganizer extends FragmentOrganizer {

public MainFragmentOrganizer(FragmentManager fragmentManager) {
    super(fragmentManager);
}

@Override
protected Fragment getInitialFragment() {
    return HomeFragment.newInstance();
}

@Override
public void onEvent(Object event){
    if(event instanceof ClickedOnPhotoEvent){
        String photoCode = ((ClickedOnPhotoEvent) event).photoCode;
        openFragment(PhotoFragment.newInstance(photoCode));
    }
}

@Override
public boolean handleBackNavigation(){
    Fragment fragment = getOpenFragment();


    if (fragment instanceof HomeFragment){
        return false;
    } else {
        fragmentManager.popBackStack();
        return true;
    }

}
}

And in your activity you just need to insatiate your FragmentManager and let it do the magic!

fragmentManager = getSupportFragmentManager();
fragmentOrganizer = new          MainFragmentOrganizer(getSupportFragmentManager());
@Override
public void onBackPressed() {
    //first let fragment organizer handle back. If it does not activity takes cares of it! 
    if(!fragmentOrganizer.handleBackNavigation()){
        finish();
    }
}
@Override
protected void onDestroy() {
    fragmentOrganizer.freeUpResources();
    super.onDestroy();
}

It may seem a lot of code but as you see most of the code encapsulated in FragmentOrganizer base class and it does all the general works so you just have to copy this file from one project to another.

As i said in the beginning i just came up with this solution right now, so it may not be perfect. I Plan to use this in my next project i hope you do to. And if you do i really appritiate if you share your though. have a good time

like image 104
Alireza A. Ahmadi Avatar answered Oct 13 '22 00:10

Alireza A. Ahmadi