fragments overlaps after activity is recreated - android

In my sample, I try to learn the difference between commitAllowingStateLoss() and commit() methods for fragments. Now I know IllegalArgumentException will appear if using commit() after activity save instance (for example, executed commit() when activity was in background).
But another problem appears, fragments overlap. I don't know why, and cannot solve the problem.
My code is as below.
In FragmentMainActivity there are two buttons for switching fragments.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="#+id/btn_fragment1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="fragment 1" />
<Button
android:id="#+id/btn_fragment2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="fragment 2" />
</LinearLayout>
<FrameLayout
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffffff"></FrameLayout>
The framelayout with id "root" is the container for fragments.
public class FragmentMainActivity extends AppCompatActivity implements View.OnClickListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment_main);
findViewById(R.id.btn_fragment1).setOnClickListener(this);
findViewById(R.id.btn_fragment2).setOnClickListener(this);
}
PlusOneFragment plusOneFragment;
PlusTwoFragment plusTwoFragment;
#Override
public void onClick(View v) {
int i = v.getId();
if (i == R.id.btn_fragment1) {
findViewById(R.id.root).postDelayed(new Runnable() {
#Override
public void run() {
final FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (getSupportFragmentManager().findFragmentByTag("tag1") == null) {
plusOneFragment = PlusOneFragment.newInstance("1", "2");
transaction.add(R.id.root, plusOneFragment, "tag1");
} else {
plusOneFragment = (PlusOneFragment) getSupportFragmentManager().findFragmentByTag("tag1");
}
if (getSupportFragmentManager().findFragmentByTag("tag2") != null) {
plusTwoFragment = (PlusTwoFragment) getSupportFragmentManager().findFragmentByTag("tag2");
transaction.hide(plusTwoFragment);
}
transaction.show(plusOneFragment).commitAllowingStateLoss();
}
}, 3000);
} else if (i == R.id.btn_fragment2) {
findViewById(R.id.root).postDelayed(new Runnable() {
#Override
public void run() {
final FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (getSupportFragmentManager().findFragmentByTag("tag2") == null) {
plusTwoFragment = PlusTwoFragment.newInstance("1", "2");
transaction.add(R.id.root, plusTwoFragment, "tag2");
} else {
plusTwoFragment = (PlusTwoFragment) getSupportFragmentManager().findFragmentByTag("tag2");
}
if (getSupportFragmentManager().findFragmentByTag("tag1") != null) {
plusOneFragment = (PlusOneFragment) getSupportFragmentManager().findFragmentByTag("tag1");
transaction.hide(plusOneFragment);
}
transaction.show(plusTwoFragment).commitAllowingStateLoss();
}
}, 3000);
}
}
}
First I click button1 to show fragment1, then click button2 to show fragment2, it works fine. After those operations, I click button1 again, and then click home, so the app is running in background. To simulated low memorey scene, I then kill the process. All those operations happened in 3s.
Finally, I opened the app from the recent records (activity recreated), found that the two fragments overlapped with each other. How does this happen?(why the problem not happened when the process is foreground?) And what can I do with this problem?

Give your fragments root element a background color (say white) android:background="#android:color/white"
and check again.
This is beacuse your fragments are not removed and added one over another.Also try replace methods instead of add.For ex:-
fragmentTransaction.replace("containerId", fragment);
as this replaces the previous fragment in layout.You can manually remove your fragment as well using
Fragment fragment = getSupportFragmentManager().findFragmentByTag(TAG_FRAGMENT);
if(fragment != null)
getSupportFragmentManager().beginTransaction().remove(fragment).commit();

As fragment remains in backstack you have to remove first fragment from backstack when second is opening. Using popBackStack may resolve your problem
Fragment fragment = (Fragment) YourClass.newInstance();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.popBackStack();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.flContent, fragment);
//TODO Add to backstack
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
I hope this may help you

Related

FindFragmentById returns null even with container id

I have an activity whose sole purpose is to show 5 different fragments in a frame layout. There are 5 buttons, each corresponding to one of the 5 fragments that can be displayed in the frame layout.
I'm trying to make it so that the activity doesn't respond to a button press of the button that corresponds to the SAME fragment, so that there are no animations or other unnecessary computation performed.
The problem is that BOTH findFragmentById and findFragmentByTag return null, even when I believe I'm passing in the correct parameter of the container view, i.e. the frame layout.
Here is how I dynamically switch the fragments when responding to button presses:
private void viewProfile(){
profile_btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Fragment profile = new ProfileFragment();
beginFragment(profile, "profile");
}
});
}
private void beginFragment(Fragment fragment, String tag) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(android.R.animator.fade_in,
android.R.animator.fade_out);
fragmentTransaction.replace(R.id.fragment_display, fragment, tag);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
Here is how I'm trying to determine what fragment is being displayed in the container:
private void launchEvents(){
final Fragment current_frag = getSupportFragmentManager()
.findFragmentById(R.id.fragment_display);
events_btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!current_frag.getTag().equals("events")) {
Fragment events = new EventsFragment();
beginFragment(events, "events");
events.setArguments(location_info);
}
}
});
}
And here is my XML file, in case I'm getting something wrong with the layout hierarchy:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/home_screen"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#323272"
tools:context=".HomeScreen">
<FrameLayout
android:id="#+id/fragment_display"
android:layout_width="match_parent"
android:layout_height="527dp"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
tools:background="#323272:color/background_dark">
</FrameLayout>
Any nudge in the right direction would be great! I feel so silly for being stuck on something so minor.
Move you findFragmentById in onClick methods as below and instant of checking a tag check the instanceof if fragment
private void launchEvents(){
events_btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Fragment current_frag = getSupportFragmentManager()
.findFragmentById(R.id.fragment_display);
if (!(current_frag instanceof EventsFragment)) {
Fragment events = new EventsFragment();
beginFragment(events, "events");
events.setArguments(location_info);
}
}
});
}

How to fix fragment transaction not showing view unless app enters and returns from background?

Currently I am trying to add a new fragment on top of an existing fragment when a RecyclerView item is clicked. The behavior is not as expected. As shown in the below video. The fragment transaction works on the first try but once I leave the fragment and return (via the bottom bar) or scroll to the bottom of the list (which triggers pagination) the transaction no longer makes the desired fragment visible when item clicked.
However if I use the device menu button and bring the app back into the foreground, the correct fragment loads becomes visible.
Another odd behavior is that once the transaction seems to no longer work if I switch tabs using the bottom bar all fragments are replaced with blank screens. This is also fixed by the above method using the device menu button.
https://youtu.be/pYfdO5UB2Zc
activity.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/view_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="#+id/bottomBar" />
<com.roughike.bottombar.BottomBar
android:id="#+id/bottomBar"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
app:bb_tabXmlResource="#xml/bottombar_tabs"
app:bb_activeTabColor="#color/blue"
app:bb_titleTextAppearance="#style/BottomBarText"/>
</RelativeLayout>
activity.java
private FragmentTransaction mFragmentTransaction;
final Fragment mJobsFragment = JobsFragment.newInstance();
final Fragment mCalendarFragment = CalendarFragment.newInstance();
final Fragment mFeedFragment = FeedFragment.newInstance();
final Fragment mSettingsFragment = SettingsFragment.newInstance();
The addFragments() and setBottomBarTabSelectListener() are both called in the onCreate of the activity.
void addFragments () {
mFragmentTransaction =
getSupportFragmentManager().beginTransaction();
mFragmentTransaction.add(R.id.view_container, mSettingsFragment,
mSettingsFragment.getTag());
mFragmentTransaction.add(R.id.view_container, mFeedFragment,
mFeedFragment.getTag());
mFragmentTransaction.add(R.id.view_container, mCalendarFragment,
mCalendarFragment.getTag());
mFragmentTransaction.add(R.id.view_container, mJobsFragment,
mJobsFragment.getTag());
mFragmentTransaction.commit();
}
public void setBottomBarTabSelectListener(){
mBottomBar.setOnTabSelectListener(new OnTabSelectListener() {
#Override
public void onTabSelected(#IdRes int tabId) {
Fragment selectedFragment = null;
mFragmentTransaction = getSupportFragmentManager().beginTransaction();
if(tabId == R.id.tab_jobs){
selectedFragment = mJobsFragment;
}
else if(tabId == R.id.tab_calendar){
selectedFragment = mCalendarFragment;
}
else if(tabId == R.id.tab_feed){
selectedFragment = mFeedFragment;
}
else if(tabId == R.id.tab_settings){
selectedFragment = mSettingsFragment;
}
mFragmentTransaction.replace(R.id.view_container, selectedFragment);
mFragmentTransaction.commit();
}
});
}
This is called when a list item is clicked.
public void openJobDetails(){
FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.view_container ,JobDetailFragment.newInstance());
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
I have tested the item clicking and they are functioning properly. So there must be something with the Fragments and the Transaction that I am missing.
try this code:
private jobDetailFragment = JobDetailFragment.newInstance();
public void openJobDetails(){
FragmentTransaction fragmentTransaction =
getChildFragmentManager().beginTransaction();
fragmentTransaction.executePendingTransactions();
if(jobDetailFragment.isAdded()){
fragmentTransaction.show(jobDetailFragment);
} else {
fragmentTransaction.add(R.id.view_container, jobDetailFragment);
}
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
I think it's better add fragment once and use hide/show to switch fragment, use isAdded() to check add status, I mean for the four bottom tabs .And maybe start a new activity for jobDetail is a better way~
And you should do something to avoid Activity State Loss. See this: https://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
I have encountered a similar problem and fixed it by re-adding fragments in onResume()
protected void onResume() {
super.onResume();
Log.d(tag,"onResume");
addFragments ();
}

Fragment show/hide is not working at all

I have been trying to build an android app for the purpose of understanding basic concepts behind fragments. But I am completely unable to show and hide fragment. Here is my code for onClickListner
final FragmentManager fragmentManager = getFragmentManager();
b.setOnClickListener(new View.OnClickListener() {
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void onClick(View v) {
FragmentTransaction ft = fragmentManager.beginTransaction();
PM_Fragment pm_fragment = new PM_Fragment();
ft.replace(android.R.id.content, pm_fragment);
if (pm_fragment.isHidden()) {
fragmentManager.beginTransaction()
.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out)
.show(pm_fragment)
.commit();
b.setText("Hide");
} else {
fragmentManager.beginTransaction()
.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out)
.hide(pm_fragment)
.commit();
b.setText("Show");
}
}
});
Fragment is simply a text line in my case. And what the button is supposed to do is toggle the visibility of the fragment.
Can anybody tell me what is wrong with this code?
And by not working, I mean that button does nothing when tapped, except for changing its text from "hide" to "show" and after that it keeps "show", no matter how many times you tap it. And this process has no effect on the behaviour of fragment at all.
I really don't understand what you are trying to do here, but you never commit ft so your Fragment is never added to the Activity. I also don't understand the purpose of the two inner FragmentTransaction, but it is save to say that you DO NOT need them at all...
Define this globally:
private PM_Fragment pmFragment = new PM_Fragment();
And your OnClickListener should look like this:
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
if(pmFragment.isAdded()) {
ft.remove(pmFragment);
} else {
ft.replace(android.R.id.content, pmFragment);
}
ft.commit();
}
});
IMPORTANT: For FragmentTransactions to work, the Fragment has to have been added in code! If you add them in XML then they cannot be affected by FragmentTransactions! So if added your Fragment like this:
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="at.example.fragments.SomeFragment" />
Or with some other similar method than I am sure that this is at least part of the problem. You need to add your Fragment solely in code like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
SomeFragment fragment = SomeFragment.newInstance();
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.flFragmentContainer, fragment);
transaction.commit();
}
}
Just replace the <fragment /> tag with something like this in the layout:
<FrameLayout
android:id="#+id/flFragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
It will serve as a container for the Fragment you want to add. You can use the FragmentTransaction above to add the Fragment to this FrameLayout.

How to add a instant of fragment again?

I have a problem with a fragment. I need to display a fragment many times and don't create new instant of it. I have a method that it use to change the content of activity.
protected void setContentFragment(Fragment contentFragment) {
this.contentFragment = contentFragment;
setContentView(R.layout.content_frame);
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_frame, contentFragment).commit();
getSlidingMenu().showContent();
}
content_frame is a simple layout.
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
The problem is: I create instant of the first Fragment and pass it to setContentFragment(...) (still keep instant of this). Then call setContentFragment with other Fragment. And now, I pass instant of the first Fragment to this method, it show bank screen. Please help me :(
Try This..
private void switchFragment(Fragment fragment) {
if (getActivity() == null)
return;
if (getActivity() instanceof MainActivity) {
DashboardActivity dashboard = (MainActivity) getActivity();
dashboard.switchFragment(fragment, "Main List");
}
}
do this
protected void setContentFragment(Fragment contentFragment) {
this.contentFragment = contentFragment;
Random random = new Random(100);
String rString = "myRandomString"+random.nextInt(); // Create a random string here everytime you call setContentFragment
setContentView(R.layout.content_frame);
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_frame, contentFragment, rString).commit();
getSlidingMenu().showContent();
}
This will replace the fragment as you require
Reference: http://developer.android.com/reference/android/support/v4/app/FragmentTransaction.html#replace%28int,%20android.support.v4.app.Fragment,%20java.lang.String%29
When swapping fragments you need to detach/reattach instead of replacing them. This leaves it in memory. Also you don't need to setContentView again, just leave the existing FrameLayout ( you could also use android.R.id.content for the main activity content view, instead of creating a FrameLayout)
Fragment content1, content2;
Fragment current=content1;
public void onCreate() {
if(savedInstanceState==null)
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content, content1)
.commit();
}
public void swap() {
if(current==content1) {
getSupportFragmentManager().beginTransaction()
.detach(content1)
.attach(android.R.id.content, content2)
.commit();
current=content2;
} else {
getSupportFragmentManager().beginTransaction()
.detach(content2)
.attach(android.R.id.content, content1)
.commit();
current=content1;
}
}

How to replace fragment C with fragment A when back button is pressed?

My scenario : Activity 1 consists of Fragments A-> B-> C. All the fragments are added using this code :
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.content, fragment, TAG);
ft.addToBackStack(TAG);
ft.commit();
Now, from fragment C, I want to directly return to Fragment A. Therefore, I've commented ft.addToBackStack(TAG) while adding Fragment C. So when I press back button from C I directly get Fragment A on the screen.
However, Fragment C is not replaced by A. In fact, both the fragments are visible. How do I solve this issue?
You need to do 2 things - name the FragmentTransaction from A->B and then override onBackPressed() in your containing activity to call FragmentManager#popBackStack (String name, int flags) when you are on Fragment C. Example:
Transition from A->B
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, new FragmentB(), "FragmentB")
.addToBackStack("A_B_TAG")
.commit();
Transition from B->C will use a similar transaction with "FragmentC" as its tag.
Then in your containing Activity override onBackPressed():
#Override
public void onBackPressed() {
if (getSupportFragmentManager().findFragmentByTag("FragmentC") != null) {
// I'm viewing Fragment C
getSupportFragmentManager().popBackStack("A_B_TAG",
FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else {
super.onBackPressed();
}
}
Theory
Use the addToBackStack(tag: String): FragmentTransaction method from within the FragmentTransaction in order to mark a point where you want to return to. This method returns the FragmentTransaction instance for chain-ability only.
Then Return with the popBackStackImmediate(tag: String, flag: int): void method from the FragmentManager. The tag is what you specified before. The flag is either the constant POP_BACK_STACK_INCLUSIVE to include the transaction marked or 0.
Example
What follows is an example with the following layout having a FrameLayout with id content_frame where the fragments are loaded into.
<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" >
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/hello_world" />
<FrameLayout
android:id="#+id/content_frame"
android:layout_below="#id/textView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
The code below marks a fragment by it's fragment class name when replacing the content of the layout element with id content_frame.
public void loadFragment(final Fragment fragment) {
// create a transaction for transition here
final FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
// put the fragment in place
transaction.replace(R.id.content_frame, fragment);
// this is the part that will cause a fragment to be added to backstack,
// this way we can return to it at any time using this tag
transaction.addToBackStack(fragment.getClass().getName());
transaction.commit();
}
And to complete this example a method that allows you to get back to that exact same fragment using the tag when you loaded it.
public void backToFragment(final Fragment fragment) {
// go back to something that was added to the backstack
getSupportFragmentManager().popBackStackImmediate(
fragment.getClass().getName(), 0);
// use 0 or the below constant as flag parameter
// FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
When implementing this for real you might want to add a null check on the fragment parameter ;-).
This is how I do it..
With FragmentA.java and any other fragments.
Replace your fragment with its class name as tag:
private void showFragment(Fragment fragment){
if(fragment != null){
getSupportFragmentManager().beginTransaction().replace(R.id.container,fragment,fragment.getClass().getSimpleName()).commit();
}
}
Now in onBackPressed():
#Override
public void onBackPressed()
{
if(getSupportFragmentManager().findFragmentByTag("FragmentA") == null)
showFragment(new FragmentA());
else
super.onBackPressed();
}

Categories

Resources