简体   繁体   中英

Communicating between Fragments Android in Java

ok, I have a simple question. Following is a very simple and basic approach to communicate between Fragments, but I've never seen anyone use it. I'm wondering what are the cons of using this approach. If anyone has any comments please let me know in the answer. Thank you

public class MyActivity extends Activity {
FragmentOne fragmentOne;
FragmentTwo fragmentTwo;

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

}

private void showFragment(Fragment fragment) {
    getSupportFragmentManager().beginTransaction().replace(R.id.fragment, fragment).commit();
}

public void showFragmentOne() {
    if(fragmentOne == null){
        fragmentOne = FragmentOne();
    }
    showFragment(fragmentOne);
}

public void showFragmentTwo(String name) {
    if(fragmentTwo == null)
        fragmentTwo = new FragmentTwo();
    fragmentTwo.setData(name);
    showFragment(fragmentTwo);
}
}

The code for fragment one:

public class FragmentOne extends Fragment {
private MyActivity myActivity;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    myActivity = getActivity() instanceOf MyActivity ? (MyActivity) getActivity() : null;
    button.setOnClickListener(() -> {
        if(myActivity != null)
          myActivity.showFragmentTwo(editText.getText().toString()); //assuming there's an edit text
    }

    return inflater.inflate(R.layout.fragment_one, container, false);
}

And the code for fragmentTwo

public class FragmentTwo extends Fragment {
private String name;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    textView.setText(name); //assuming there's a textview
    return inflater.inflate(R.layout.fragment_two, container, false);
}

public void setData(String name){
    this.name = name;
}
}

PS please ignore the indentation etc, I didn't copy this code I actually typed the whole thing.

FragmentOne fragmentOne; FragmentTwo fragmentTwo; public void showFragmentOne() { if(fragmentOne == null){ fragmentOne = FragmentOne(); } //... } public void showFragmentTwo(String name) { if(fragmentTwo == null) { fragmentTwo = new FragmentTwo(); } //...

This will break after low-memory condition , when Android recreates your Fragment instances using their no-arg constructor, rather than using the instances you're creating here.

Although technically in this particular case, you are using fragmentManager.beginTransaction().replace().commit() , therefore your instance will win, and the one recreated by the system will be overridden. As you used replace instead of add , you also won't end up with "multiple overlapping fragments".

However, you lose the ability to restore the Fragment via Fragment.onSaveInstanceState / Fragment.onCreate(Bundle) , as you will be overwriting the system-recreated fragment using your own uninitialized fragment.

Basically, this approach results in state-loss.


Out of the box, the intended solution is to use

public class MyActivity extends Activity {

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

        if(savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction().replace(R.id.fragment, new FragmentOne(), "one").commit();
        }        
    }

    public void showFragmentTwo(String name) {
        FragmentTwo fragmentTwo = new FragmentTwo();
        Bundle bundle = new Bundle();
        bundle.putString("name", name);
        fragmentTwo.setArguments(bundle);
        getSupportFragmentManager().beginTransaction()
                  .replace(R.id.fragment, fragmentTwo, "two")
                  .addToBackStack(null)
                  .commit();
    }
}

And you could potentially find the fragments using findFragmentByTag .


Although I personally don't like the Fragment backstack, I believe it's much easier if you track identifiers that describe the fragments you should have, these identifiers can provide you the given tag, and you can set up the fragments to whatever state you want them to be without necessarily removing the fragment (and therefore losing its view + state) just because you navigated forward.

You can check out the approach I tend to use for fragments here .

Using the following code, I could set up any Fragment to be in any state that I wanted, without relying on addToBackStack .

public class FragmentStateChanger {
    private FragmentManager fragmentManager;
    private int containerId;

    public FragmentStateChanger(FragmentManager fragmentManager, int containerId) {
        this.fragmentManager = fragmentManager;
        this.containerId = containerId;
    }

    public void handleStateChange(StateChange stateChange) {
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.disallowAddToBackStack();

        // here you could animate based on direction
        List<Key> previousKeys = stateChange.getPreviousKeys();
        List<Key> newKeys = stateChange.getNewKeys();
        for(Key oldKey : previousKeys) {
            Fragment fragment = fragmentManager.findFragmentByTag(oldKey.getFragmentTag());
            if(fragment != null) {
                if(!newState.contains(oldKey)) {
                    fragmentTransaction.remove(fragment);
                } else if(!fragment.isDetached()) {
                    fragmentTransaction.detach(fragment);
                }
            }
        }
        for(Key newKey : newKeys) {
            Fragment fragment = fragmentManager.findFragmentByTag(newKey.getFragmentTag());
            if(newKey.equals(stateChange.topNewKey())) {
                if(fragment != null) {
                    if(fragment.isRemoving()) {
                        fragment = newKey.createFragment();
                        fragmentTransaction.replace(containerId, fragment, newKey.getFragmentTag());
                    } else if(fragment.isDetached()) {
                        fragmentTransaction.attach(fragment);
                    }
                } else {
                    fragment = newKey.createFragment();
                    fragmentTransaction.add(containerId, fragment, newKey.getFragmentTag());
                }
            } else {
                if(fragment != null && !fragment.isDetached()) {
                    fragmentTransaction.detach(fragment);
                }
            }
        }
        fragmentTransaction.commitAllowingStateLoss();
    }
}

And then I could use this like this :

public class MainActivity
        extends AppCompatActivity
        implements StateChanger {
    private static final String TAG = "MainActivity";

    @BindView(R.id.fragment)
    ViewGroup root;

    FragmentStateChanger fragmentStateChanger;

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

        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);

        fragmentStateChanger = new FragmentStateChanger(getSupportFragmentManager(), R.id.fragment);

        Navigator.configure()
                .setStateChanger(this)
                .install(this, root, History.single(FragmentOneKey.create()));
    }

    @Override
    public void onBackPressed() {
        if(!Navigator.onBackPressed(this)) {
            super.onBackPressed();
        }
    }


    public void showSecondFragment(String data) {
         Navigator.getBackstack(this).goTo(FragmentTwoKey.create(data));
    }

    // this handles navigation from any nav state to any other nav state
    @Override
    public void handleStateChange(@NonNull StateChange stateChange, @NonNull Callback completionCallback) {
        if(stateChange.isTopNewKeyEqualToPrevious()) {
            completionCallback.stateChangeComplete();
            return;
        }
        fragmentStateChanger.handleStateChange(stateChange);
        completionCallback.stateChangeComplete();
    }
}

Can't really imagine Fragments anymore without the convenience of saying backstack.goTo(SomeScreen()) instead of juggling transactions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM