I am having a problem with fragment ordering when the screen orientation changes during a custom animation of my fragment transition.
If I rotate the screen at exactly the right time, the fragments are added with the MyFragment2 in the position where MyFragment1 should be.
I am adding my fragments as follows:
final FragmentManager fm = activity.get().getFragmentManager();
final FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations(
R.animator.slide_in_left,
R.animator.slide_out_top,
R.animator.slide_in_bottom,
R.animator.slide_out_right);
ft.replace(R.id.container,
MyFragment1.newInstance(), MyFragment1.TAG);
ft.add(R.id.container,
MyFragment2.newInstance(),MyFragment12.TAG)
.addToBackStack(null)
.commit();
I have been searching for many hours for information about this problem. I have seen information here Android multiple fragment transaction ordering, here https://code.google.com/p/android/issues/detail?id=69403&thanks=69403&ts=1399482444 and here https://code.google.com/p/android/issues/detail?id=31116
My understanding is that this is a bug with re-adding fragments during onResume() of an activity.
How can I prevent my Activity from incorrectly ordering my fragments when it is resumed?
My Activity layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
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"
tools:ignore="MergeRootFrame" />
I had the same issue, the following workaround helped:
#Override
public void onResume() {
super.onResume();
bringToFrontIfNeeded();
}
#Override
public void onBackStackChanged() {
bringToFrontIfNeeded();
}
private void bringToFrontIfNeeded() {
// A fix for a weird google bug
if (amIOnTop() && (getView() != null)) {
getView().bringToFront();
}
}
private void amIOnTop() {
// depends on you app logic, can be something like
FragmentManager manager = getFragmentManager();
int count = manager.getBackStackEntryCount();
return (BACK_STACK_ENTRY_NAME.equals(manager.getBackStackEntryAt(count - 1).getName()));
}
try adding the fragment in the activity xml layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<fragment android:name="com.example.android.fragments.HeadlinesFragment"
android:id="#+id/headlines_fragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.android.fragments.ArticleFragment"
android:id="#+id/article_fragment"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
Related
I am working in a Fragment. it contains 2 Buttons and a FrameLayout:
<FrameLayout 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"
tools:context="fragments.Fragment_Book_A_Cam">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2">
<Button
android:id="#+id/bt_Public"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1"
android:background="#drawable/red"
android:text="Public Jobs "
android:textColor="#FFFFFF" />
<Button
android:id="#+id/bt_Active"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_weight="1"
android:background="#drawable/black"
android:text="Active Jobs"
android:textColor="#FFFFFF" />
</LinearLayout>
<FrameLayout
android:id="#+id/Fragment_Jobs_BookACam"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</LinearLayout>
I want to load another Fragment in the FrameLayout on a Button click and don't want to replace the main FrameLayout.
All the methods I have used either crash or replace the main Fragment.
bt_Public.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// fragment = new Fragment_MyCloudVideo();
// FragmentManager fragmentManager = ;
// FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
// fragmentTransaction.replace(R.id.Fragment_MyVideos, fragment);
// fragmentTransaction.commit();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment_Public_Jobs fragment = new Fragment_Public_Jobs();
fragmentTransaction.addToBackStack("xyz");
fragmentTransaction.hide(Fragment_Book_A_Cam.this);
fragmentTransaction.add(android.R.id.content, fragment);
fragmentTransaction.commit();
}
});
You never seem to use the Fragment_Jobs_BookACam FrameLayout id
If you want to show Fragments inside Fragments, you need to use the method to get the child FragmentManager
First of all correct the errors present in your xml file and try replace() method of fragmentTransaction instead add().
I've been trying to solve this problem for a few days now and still cant figure it out. I've checked many similiar topics about this but didnt get me what I need.
As you can see I'm adding the fragments dynamically:
public void displayFragment1() {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, new Fragment_1(), "frag1");
fragmentTransaction.commit();
}
public void displayFragment2() {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, new Fragment_2(), "frag2");
fragmentTransaction.commit();
}
Here is the part that I cant figure out. I'm searching the fragments by tag and it seems that its not working since I'm always getting nullpointer exception or classexception. Toast just shows the value of the text that I entered so its sending the data from a fragment to the activity. so that part is fine. Any ideas guys???
#Override
public void inputValue(String text) {
if (text != null) {
Toast.makeText(MainActivity.this, text, Toast.LENGTH_SHORT).show();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, new Fragment_2(), "frag2");
fragmentTransaction.commit();
FragmentManager fm = getSupportFragmentManager();
Fragment_2 fragment = (Fragment_2) fm.findFragmentByTag("frag2");
fragment.setChangeTo(text);
} else {
Toast.makeText(MainActivity.this, "didnt recieve the value", Toast.LENGTH_SHORT).show();
}
}
activity main xml
<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:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
tools:context="com.example.neven.question_for_stackoverflow.MainActivity"
android:background="#drawable/my_background">
<FrameLayout android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fragment_container">
</FrameLayout>
MainActivity:
public class MainActivity extends AppCompatActivity implements Fragment_1.sendData {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
displayFragment1();
}
fragment 1 xml:
<FrameLayout 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"
tools:context="com.example.neven.question_for_stackoverflow.Fragment_1"
android:background="#drawable/my_background">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="#string/hello_blank_fragment"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="fragment 1"
android:id="#+id/textView" android:layout_gravity="center"/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="#+id/etInputID" android:layout_gravity="center" android:layout_marginTop="50dp"
android:hint="enter text here" android:textColorHint="#e3ca0a"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send"
android:id="#+id/bSendID" android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="130dp"/>
fragment 2 xml
<FrameLayout 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"
tools:context="com.example.neven.question_for_stackoverflow.Fragment_2"
android:background="#drawable/my_background">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="#string/hello_blank_fragment"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="fragment 2 - change me"
android:id="#+id/tvChangeMeID" android:layout_gravity="center"/>
The issue seems to be that you are calling displayFragment1(); in your Activity's onCreate and yet you are expecting to find Fragment_2 which you are only setting/initializing in displayFragment2(); - you never call this method.
So, please change your code in the onCreate method to call displayFragment2() like this:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
displayFragment2();
}
UPDATE:
Please add the call getSupportFragmentManager().executePendingTransactions() after the commit statement and then try looking up the Fragment by TAG and see if this at least returns a Fragment object and not null. Your code will now look like:
...
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, new Fragment_2(), "frag2");
fragmentTransaction.commit();
getSupportFragmentManager().executePendingTransactions()
Fragment_2 fragment = (Fragment_2) getSupportFragmentManager().findFragmentByTag("frag2");
fragment.setChangeTo(text);
I hope this helps.
You should declare a FrameLayout in your Activity's layout xml where the fragments you want will be inflated. Then use that FrameLayout's id when making a transaction instead of android.R.id.content.
The way to handle this is:
Fragment fragment = getActivity().getFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof YourFragmentClass) {
fragment.setChangeTo(value);
}
I've developed a material NavigationDrawer which has some menus. Each menu is associated with individual fragments. I would like to keep my FragmentHome() always alive in backstack so that whenever I switch to different fragment and come back, the FragmentHome() should be there as before.
This is the xml where I'm creating the fragments,
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical" >
<include layout="#layout/toolbar" />
<android.support.v4.widget.DrawerLayout
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<fragment
android:id="#+id/navigation_drawer"
android:name="NavigationDrawerFragment"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start" />
</android.support.v4.widget.DrawerLayout>
</LinearLayout>
This is how I'm creating and replacing the fragments,
#Override
public void onNavigationDrawerItemSelected(int position) {
Fragment fragment = null;
String title = getString(R.string.app_name);
switch (position) {
case 0:
fragment = new FragmentHome();
title = getString(R.string.app_name);
break;
case 1:
fragment = new FragmentCategories();
title = getString(R.string.title_categories);
case 2:
fragment = new FragmentMyPlaylist();
title = getString(R.string.title_playlist);
if (fragment != null) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.container, fragment);
fragmentTransaction.commit();
// set the toolbar title
getSupportActionBar().setTitle(title);
}
}
The major problem I'm facing is, each fragment is created from the scratch whenever I'm replacing them. This is annoying and all of the components of the HomeFramgent() is re-created once again when they have already been created once.
UPDATE:
I made another solution for this issue which is to create individual framents in xml and replace them via the main container,
For this first I've created individual fragments that are associate with the fragment classes,
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical" >
<include layout="#layout/toolbar" />
<android.support.v4.widget.DrawerLayout
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<fragment
android:id="#+id/FragmentHome"
android:name="FragmentHome"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<fragment
android:id="#+id/FragmentCategories"
android:name="FragmentCategories"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<fragment
android:id="#+id/FragmentMyPlaylist"
android:name="FragmentMyPlaylist"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<fragment
android:id="#+id/navigation_drawer"
android:name="NavigationDrawerFragment"
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start" />
</android.support.v4.widget.DrawerLayout>
</LinearLayout>
Then I'm declaring each fragment by id in the main activity,
FragmentManager fm = getSupportFragmentManager();
fragments[HOME] = fm.findFragmentById(R.id.FragmentHome);
fragments[CATEGORIES] = fm.findFragmentById(R.id.FragmentCategories);
fragments[PLAYLIST] = fm.findFragmentById(R.id.FragmentMyPlaylist);
FragmentTransaction transaction = fm.beginTransaction();
for (int i = fragments.length - 1; i > 0; i--) {
transaction.hide(fragments[i]);
}
transaction.commit();
Now whenver I want to replace a frament I'm doing this inside onNavigationDrawerItemSelected(),
fragmentManager.beginTransaction().replace(R.id.container, new
FragmentHome()).commit();
This solves the issue of re-creating each fragments and is created once after the application runs. Again this gives me a restriction of not refreshing the other fragments. All fragments are created at once and not re-creating on change, which I don't want.
I'm looking for an efficient way so that only the FragmentHome() will stay in backstace and all other fragments will get refreshed while replacing.
Any kind of help would be greatly appreciated...
Use FragmentManager.saveFragmentInstanceState to capture the state of your HomeFragment before it is replaced. You will need to declare a variable in the activity that hosts your fragments to save the result. When the HomeFragment is reselected, use Fragment.setInitialSavedState to restore the saved state of HomeFragment before adding it to the FragmentManager.
I created an activity with a navigation drawer and a FrameLayout. This frame layout should contain the fragment which corresponds the selected navigation item. So I followed the instructions in android tutorials, but it doesn't work.
Problem:
It seems to me the fragments are only added and not replaced. What am I doing wrong?
My activity xml:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mainViewDrawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<FrameLayout
android:id="#+id/mainViewContentFrame"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="#dimen/drawer_content_padding" >
</FrameLayout>
<ListView android:id="#+id/mainViewDrawerList"
android:layout_width="#dimen/drawer_size"
android:layout_height="match_parent"
android:layout_gravity="start"
android:choiceMode="none"
android:divider="#android:color/transparent"
android:dividerHeight="0dp"
android:background="#color/drawer_background"
/>
</android.support.v4.widget.DrawerLayout>
my first fragment xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingLeft="#dimen/activity_vertical_margin"
android:paddingRight="#dimen/activity_vertical_margin"
android:paddingTop="#dimen/activity_horizontal_margin"
android:paddingBottom="#dimen/activity_horizontal_margin"
android:id="#id/fragmentProfile">
<TextView
style="#android:style/TextAppearance.Small"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="#string/mainViewProfileGivenNameLabel"
/>
<TextView
android:id="#+id/profileGivenNameTextView"
style="#android:style/TextAppearance.Large"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
my second fragment xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#id/fragmentProtocol">
<ListView android:id="#+id/protocolTableListView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:transcriptMode="alwaysScroll"
android:cacheColorHint="#00000000"
android:listSelector="#android:color/transparent"/>
</LinearLayout>
my transition method in the activity:
private void switchToFragment(int navigationId, boolean addToBackStack){
FragmentManager fragmentManager = getFragmentManager();
String fragmentTag = null;
boolean fragmentCreated = false;
switch(navigationId) {
case NavigationItemProvider.NAVIGATIONITEM_PROFILE:
fragmentTag = NAVIGATIONTAG_PROFILE;
break;
case NavigationItemProvider.NAVIGATIONITEM_PROTOCOL:
fragmentTag = NAVIGATIONTAG_PROTOCOL;
break;
}
// determine if the fragment is already instanciated otherwise create
FragmentBase fragment = (FragmentBase)fragmentManager.findFragmentByTag(fragmentTag);
if (fragment == null){
if (fragmentTag == NAVIGATIONTAG_PROFILE) {
fragment = new ProfileFragment();
fragmentCreated = true;
}
else if(fragmentTag == NAVIGATIONTAG_PROTOCOL) {
fragment = new ProtocolFragment();
fragmentCreated = true;
}
}
// switch to fragment
if (fragment.isVisible()){
return;
}
FragmentTransaction transaction = fragmentManager.beginTransaction();
if (fragmentCreated) {
transaction.replace(R.id.mainViewContentFrame, fragment, fragmentTag);
}
else{
transaction.show(fragment);
}
if(addToBackStack)
{
transaction.addToBackStack(null);
}
transaction.commit();
}
solved the problem:
In onCreate I returned the result of super.onCreate and not the result of Layoutinfalter.inflate and in addition I didn't call inflate with last parameter set to false.
Why after changing fragments from the first(pageNews) to the second(pageViewFromWeb) "findFragmentById" returns a reference to the first(pageNews) fragment?
layout:
<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"
tools:context=".MainActivity"
android:paddingTop="10dp"
android:background="#000">
<FrameLayout
android:id="#+id/frgmCont"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
</FrameLayout>
</RelativeLayout>
code:
fTrans = getSupportFragmentManager().beginTransaction();
fTrans.add(R.id.frgmCont, pageNews);
....
....
public void selectNews(String news_id) {
// Toast.makeText(this, news_id, Toast.LENGTH_SHORT).show();
fTrans = getSupportFragmentManager().beginTransaction();
fTrans.replace(R.id.frgmCont, pageViewFromWeb);
fTrans.commit();
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.frgmCont);
...
}
Its because replace() does not execute immediately, but the FragmentTransaction is scheduled. When you findFragmentById() immediately after replacing fragments, they are not replaced yet...