Dynamically replacing a fragment in ViewPager with TabsAdapter - android

I am using a ViewPager in conjunction with ActionBar tabs, as illustrated here. I'm using ActionBarSherlock for backward compatibility, so the parent activity extends SherlockFragmentActivity, and the children fragments extend SherlockFragment.
The solution works great for tabs with swiping, but now I want to dynamically change a fragment associated with one of the tabs.
I've read through numerous S.O. answers on this subject (for example here and here), but I've not found a clear explanation of how to dynamically change a fragment when using ViewPager + the TabsAdapter above.
Here's what I have now. When the user hits a button on an existing fragment, I try to replace the fragment from the parent activity as follows:
AnotherFragment fragment = new AnotherFragment();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
int viewId = R.id.pager; // resource id of the ViewPager
int position = Constants.TAB_2; // tab pos of the fragment to be changed
String tag = "android:switcher:" + viewId + ":" + position;
ft.replace(viewId, fragment, tag);
ft.commit();
mTabsAdapter.notifyDataSetChanged();
This doesn't work, so I'm missing something. I also tried doing it with nested fragments using getChildFragmentManager(), but ran into a problem since this function isn't available without API 17, Android 4.2.
Thanks for any help!

I've made a little example that shows a similar behaviour. I hope you can reuse it.
I think the key is to use a fragment as a container and replace your "real" fragment using it.
For example, see how to navigate to another fragment:
btn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
FragmentTransaction trans = getFragmentManager()
.beginTransaction();
/*
* IMPORTANT: We use the "root frame" defined in
* "root_fragment.xml" as the reference to replace fragment
*/
trans.replace(R.id.root_frame, new SecondFragment());
/*
* IMPORTANT: The following lines allow us to add the fragment
* to the stack and return to it later, by pressing back
*/
trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
trans.addToBackStack(null);
trans.commit();
}
});
You can check the entire example here:
https://github.com/danilao/fragments-viewpager-example

Related

How to replace fragment properly using navigation drawer

I am using navigation drawer and it is simple to use. I am not providing the complete code but providing you detail which could be easy for you to understand my problem. I am using fragments these are about 8 in numbers and I am replacing them with one an other. But here comes a problem
I am replacing them on click event of the navigation drawer. but there are two main problems
After replacement , I can see the previous fragment in the background. does replace method just call the new fragment over it ? if yes then what should I do to old fragment not be visible in the background of my new fragment.
When I click navigation drawer Item , it loads the specific fragment successfully. but keeping in that fragment when I click to that specific item again it loads this fragment again and again. For example if drawer item num 3 opens fragment MyBook , then by clicking item num three 2 or many times would open fragment that much time.
So please some one answer me how to cure my app for such kind of actions which I described above.
I tried like this. Its working fine me
FragmentManager frgmanager = getFragmentManager();
frgmanager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
FragmentTransaction frgTransaction = frgmanager.beginTransaction();
if(subitem.equalsIgnoreCase("subitem")){
Frag1 frg1 =new Frag1(mCtx);
frgTransaction.replace(R.id.inflate_layout, frg1);
}else if(subitem1.equalsIgnoreCase("subitem1")){
Frag2 frg2 =new Frag2(mCtx);
frgTransaction.replace(R.id.inflate_layout, frg2);
}else{
Frag2 frg3 =new Frag3(mCtx);
frgTransaction.replace(R.id.inflate_layout, frg3);
}
frgTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
frgTransaction.commit();
you can use addtobackstack in fragmentstranstion object.like
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.bodyfragment, new AnotherFragment());
transaction.addtoBackStack(null).commit();
Use replace-method of FragmentTransaction instead of add (http://developer.android.com/guide/components/fragments.html#Transactions)
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.bodyfragment, new AnotherFragment());
transaction.commit();
To avoid re-instantiating the fragment, keep track of the current open fragment and only do a fragment transaction, if we next-to-be-opened fragment is a different one than the current.
This may achieved like the following:
class MyActivity ... {
private String currentFragment;
private void openNewFragment(Fragment fragment) {
String newFragment = fragment.getClass().getSimpleName();
if (newFragment.equals(currentFragment)){
// new fragment already shown
return;
}
// Fragment transaction etc here:
}
}
Note that this only compares fragments based in their class name. Sometimes this might not be unique, e.g. if there is a DetailFragment class which displays information about an entity. Which entities details to show may depend on intent arguments.
The above code however will then prevent opening DetailFragment for Entity=1 if currently details for Entity=2 are shown. For these scenarios the information about the fragment kept needs to be extended (e.g. storing a Reference or WeakReference to the fragment instance itself).

Add fragment into a fragment (nested fragment)

I want to add a youtube fragment into my already existing fragments dynamically. The code I used is below:
// setting the Youtube Player Dynamically
private int setYoutubePlayer(String desc, View view, int prevID,
Bundle input) {
if (desc.indexOf("=") != -1) {
desc = desc.substring(desc.indexOf("=") + "=".length());
} else {
return prevID;
}
final String url = desc;
LinearLayout videoLayout = new LinearLayout(view.getContext());
videoLayout.setOrientation(LinearLayout.VERTICAL);
prevID++;
videoLayout.setId(prevID);
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
fragment.setVideoId(url);
LinearLayout itemLayout = (LinearLayout) view.findViewById(R.id.items);
itemLayout.addView(videoLayout);
fragmentTransaction.add(itemLayout.getId(), fragment,
"youtube fargment " + prevID);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
return prevID;
}
I need to get the youtube fragment in the appropriate fragment. As I checked when always a new fragment get loaded (when swipe between fragments), the new inner fragment needs to be the first loaded fragment.
Any help will be gladly accepted.
SOLVED: Thank you Koby You were right. i had to replace "getActivity().getSupportFragmentManager();" with "getChildFragmentManager()". The problem was apparently the Sherlock library came with a old android v4 support library. I had to update the support library in the Sherlock. It worked for me.....
to create a nested fragment inside a fragment, you should use:
http://developer.android.com/reference/android/app/Fragment.html#getChildFragmentManager()
call the getChildFragmentManager() from the parent fragment,
and do the transaction in the parent to nest the child inside.
https://stackoverflow.com/a/13381825/1693674
tell me if you need more help in doing that...

Actionbar Tab Navigation design/issues

I'm attempting to implement a pretty basic navigation structure using Actionbar tabs and fragments. Basically I have three fragments which are selected using the tabs on an actionbar. Each one of these fragments is a listview which should allow the user to select an item and open a detail view.
I'm wondering the best way to add a new fragment to the screen from an existing fragment. I currently have the detail views all implemented as activities (which obviously is not ideal since the action bar tabs aren't there, and navigation using the back button returns to tab 1 regardless of current location).
I'm adding all of the list fragments in my MainActivity;
public void onTabSelected(Tab tab, FragmentTransaction ft) {
if (tab.getPosition() == 0) {
Frag1 frag = new Frag1();
ft.replace(android.R.id.content, frag);
} else if (tab.getPosition() == 1) {
Frag2 frag = new Frag2();
ft.replace(android.R.id.content, frag);
} else if (tab.getPosition() == 2) {
Frag3 frag = new Frag3();
ft.replace(android.R.id.content, frag);
}
}
What do I need to implement on those fragments to launch a detail view? I've attempted something like this, but navigation still does not work how I would like it.
private void onListItemClick(View v, int pos, long id) {
Log.d(TAG, "Clicked at position: " + pos);
NewsModel selectedModel = newsItems.get(pos);
Log.d(TAG, "Item: " + selectedModel.getTitle());
NewsDetailFragment fragment = NewsDetailFragment.newInstance(selectedModel);
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.content, fragment);
ft.addToBackStack(null);
ft.commit();
I'm coming generally from the iOS world so some of my paradigms might be a little out of wack with how Android is meant to function.
Thanks!
As stated in the Supporting Tablets and Handsets training document, the standard way for using Fragments for showing a master/detail view is as separate activities on handsets (when you don't have enough room to show both) and side by side on tablets. The SDK comes with a Master/Detail sample which implements this suggestion (if you are using Eclipse, go to New->Other->Android->Android Activity->Master/Detail Flow).
You can combine this training with the tab code you already have (replacing the left side fragment when tabs are selected, probably also want to remove the detail view on the right hand side when a tab is selected as well) to get a layout that works on all Android devices.
Some additional information you might find useful is Multi-pane layout design guidelines and the entire Fragment Training set.

How can I replace a fragment used by the tabs in ActionBarSherlock using the Tabs and Pager example?

My question looks similar to this. However, it is a bit more in general:
Let me try to explain: I am using the ActionBarSherlock with the Tabsadapter and Viewpager. I have replaced all the fragments with my own and now I have a button in one of my fragments which, when clicked on, starts the following onclick handler:
public void onAcceptSelected() {
SherlockFragment addFirstChildFragment = AddChildFragment.newInstance();
getSupportActionBar().setTitle("The new title of the fragment here");
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.pager, addFirstChildFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
The problem is then that the following method in the new fragment:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_childeditor, container, false);
return v;
}
does not result in actually providing a new visible layout: the tab its contents are empty (e.g. I see a holo white themed empty tab).
Now I have the following questions:
My main question is: "How should I replace fragments within a tab?"
with subquestions:
Should I adapt the XML of the Tabs and Pager example and fit it with fragment containers?
What is the best practice for using tabs and a viewpager with many fragments? Because I want to use the tabs as my main navigation, but I will have plenty of different fragments.
Its fairly simple. Just change the fragment associated to the tab like the way you replace other fragment.
Fragment donorpage = new DonorRegistrationPage(); // new fragment for the tab
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, donorpage, "frag");
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
This should replace the fragment of the tab.
The answer resides in the answers to this question
Actionbarsherlock + tabs + multi fragments?
Answers to my questions are:
1. No this is not necessary: I can add them programaticllay.
2. I can use fragmentstacks as the example shows in the link above.

Replacing ViewPager with Fragment - Then Navigating Back

I've got an activity which initially hosts a ViewPager, hooked up to a FragmentPagerAdapter.
When the user clicks on an item inside the ViewPager's child fragment, I'm using a FragmentTransaction to replace an empty container view with a new Fragment which I want to navigate to.
If I use addToBackStack() on the transaction, commit the transaction and then navigate back, I am not returned to the ViewPager's views (the initial layout).
If I don't use addToBackStack() on the transaction, commit the transaction and then navigate back, the application exits.
It seems apparent that the ViewPager is not added to the backstack (which is not that surprising as it isn't a fragment in itself).. But I would expect the default behaviour would be that the back press takes me back to that activities initial View (the ViewPager).
Based on what I've read, it seems that perhaps because a fragment transaction is taking place, the ViewPager or PagerAdapter loses track of which fragment should be on display.
I'm really confused with this, but I ended up creating a huge mess of code overriding the onBackPress and showing and hiding the viewpager views. I would've thought there is a simpler way to use default behaviours to perform the appropriate navigation.
tl;dr
A is a Viewpager hosting fragments.
B is a new Fragment.
When I replace A with B, and then press back, I expect to navigate back to A, but that is not happening.
Any advice would be much appreciated.
Code:
MainActivity:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
headingLayout = (RelativeLayout) findViewById(R.id.headingLayout);
headingLayout.setVisibility(View.GONE);
// Set up the ViewPager, attaching the adapter and setting up a listener
// for when the
// user swipes between sections.
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setPageMargin(8);
/** Getting fragment manager */
FragmentManager fm = getSupportFragmentManager();
/** Instantiating FragmentPagerAdapter */
MyFragmentPagerAdapter pagerAdapter = new MyFragmentPagerAdapter(fm);
/** Setting the pagerAdapter to the pager object */
mViewPager.setAdapter(pagerAdapter);
.
.
.
}
public void onListItemClicked(Fragment fragment) {
fromPlayer = false;
InitiateTransaction(fragment, true);
}
public void InitiateTransaction(Fragment fragment, boolean addToBackStack) {
invalidateOptionsMenu();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragmentContainer, fragment).addToBackStack(null)
.commit();
}
PagerAdapter:
package another.music.player;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import another.music.player.fragments.AlbumListFragment;
import another.music.player.fragments.ArtistListFragment;
import another.music.player.fragments.SongListFragment;
public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
final int PAGE_COUNT = 3;
/** Constructor of the class */
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
/** This method will be invoked when a page is requested to create */
#Override
public Fragment getItem(int i) {
switch (i) {
case 0:
ArtistListFragment artistListFragment = new ArtistListFragment();
Bundle artistData = new Bundle();
artistData.putInt("current_page", i + 1);
artistListFragment.setArguments(artistData);
return artistListFragment;
case 1:
AlbumListFragment albumListFragment = new AlbumListFragment();
Bundle albumData = new Bundle();
albumData.putInt("current_page", i + 1);
albumData.putBoolean("showHeader", false);
albumListFragment.setArguments(albumData);
return albumListFragment;
default:
SongListFragment songListFragment = new SongListFragment();
Bundle songData = new Bundle();
songData.putInt("current_page", i + 1);
songListFragment.setArguments(songData);
return songListFragment;
}
}
/** Returns the number of pages */
#Override
public int getCount() {
return PAGE_COUNT;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "Artists";
case 1:
return "Albums";
default:
return "Songs";
}
}
}
main xml (containing fragmentContainer & ViewPager):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/main_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/app_background_ics" >
<RelativeLayout
android:id="#+id/headingLayout"
android:layout_width="match_parent"
android:layout_height="56dp" >
</RelativeLayout>
<FrameLayout
android:id="#+id/fragmentContainer"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_below="#+id/headingLayout" />
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
<android.support.v4.view.PagerTabStrip
android:id="#+id/pager_title_strip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#33b5e5"
android:paddingBottom="4dp"
android:paddingTop="4dp"
android:textColor="#fff" />
</android.support.v4.view.ViewPager>
</RelativeLayout>
I also had this very same problem for a long time. The solution turns out to be very simple, and you don't need any hacks with the ViewPager Visibility. I is described in this other SO related question: Fragment in ViewPager not restored after popBackStack
However, to make it simple, all you need is to use getChildFragmentManager() in your ViewPager adapter, instead of getSupportFragmentManager(). So, instead of this:
/** Getting fragment manager */
FragmentManager fm = getSupportFragmentManager();
/** Instantiating FragmentPagerAdapter */
MyFragmentPagerAdapter pagerAdapter = new MyFragmentPagerAdapter(fm);
/** Setting the pagerAdapter to the pager object */
mViewPager.setAdapter(pagerAdapter);
You do this:
/** Getting fragment manager */
FragmentManager fm = getChildFragmentManager();
/** Instantiating FragmentPagerAdapter */
MyFragmentPagerAdapter pagerAdapter = new MyFragmentPagerAdapter(fm);
/** Setting the pagerAdapter to the pager object */
mViewPager.setAdapter(pagerAdapter);
UPDATE :
That's not the "Android way" and it results in bad user experience for the case of a listview. Instead, create a new activity.
For people searching for a simple solution to this problem, I'll just sum up what I did.
My architecture :
ViewPager in FragmentActivity (ActionBarActivity actually, for ActionBar support. But ActionBarActivity implements FragmentActivity).
2 tabs :
FragmentContainer1 that extends Fragment.
FragmentContainer2 that extends Fragment.
For each FragmentContainer, we call getChildFragmentManager, in the onCreate method for example, and add the fragment we want to show in this container :
FragmentToShow fragment = new FragmentToShow();
getChildFragmentManager()
.beginTransaction()
.add(R.id.container, fragment)
.commit();
We don't want our first fragment to be added to the backstack of the fragment container because we don't want to show the fragment container if we press the back button.
Then, if we want to replace FragmentToShow by another fragment in our FragmentToShow class (like with a listView) :
Fragment itemFragment = new ItemFragment();
getFragmentManager()
.beginTransaction()
.replace(R.id.container, itemFragment)
.addToBackStack(null)
.commit();
Here we retrieve the child fragment manager, and we add the itemFragment to the back stack.
So now we want, on pressing the back button, to go back to the listView (the FragmentToShow instance). Our activity (FragmentActivity) is the only one aware of the back button, so we have to override the method onBackPressed() in this activity :
#Override
public void onBackPressed() {
// We retrieve the fragment manager of the activity
FragmentManager frgmtManager = getSupportFragmentManager();
// We retrieve the fragment container showed right now
// The viewpager assigns tags to fragment automatically like this
// mPager is our ViewPager instance
Fragment fragment = frgmtManager.findFragmentByTag("android:switcher:" + mPager.getId() + ":" + mPager.getCurrentItem());
// And thanks to the fragment container, we retrieve its child fragment manager
// holding our fragment in the back stack
FragmentManager childFragmentManager = fragment.getChildFragmentManager();
// And here we go, if the back stack is empty, we let the back button doing its job
// Otherwise, we show the last entry in the back stack (our FragmentToShow)
if(childFragmentManager.getBackStackEntryCount() == 0){
super.onBackPressed();
} else {
childFragmentManager.popBackStack();
}
}
Since we call getSupportFragmentManager in our activity, we just can call getFragmentManager in our child fragments. This will return a support FragmentManager instance.
And that's it! I'm not an expert, so if you have suggestions or remarks, feel free.
The only way I've found to achieve this is to do the following:
When navigating away from the viewPager, send the viewPager out of view using Visiblity.GONE. Add any fragment transactions to the backstack.
When returning to the viewPager screen (via a back press), override the onBackPressed. You can check to see how many fragments are in the backstack. If the viewPager was the first view before fragment transactions took place, then you can check to see if the fragment backstack entry count is 0.
fragmentManager.getBackStackEntryCount() == 0, there are no fragments in the backstack.
If that statement is true, then just bring the viewPager back into view using Visibility.VISIBLE.
If you are using a ViewPager containing Fragments that can start Activities, here is how you would properly navigate back to the position in the ViewPager, upon hitting back or navigating up from said Activity (Assumes you have Upward navigation declared for your Activities).
First off you need to pass the current position of the ViewPager as an Extra in the Intent to start the new Activity.
Then you will pass back that position to the parent Activity doing this:
Intent upIntent = NavUtils.getParentActivityIntent(this);
upIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
upIntent.putExtra(FeedPagerActivity.EXTRA_POSITION, position);
NavUtils.navigateUpTo(this, upIntent);
Put that code within
onOptionsItemSelected(MenuItem item)
I think I've had a very similar problem.
It seems that FragmentManagers behave in somewhat hierarchical way. The way I've solved this problem, was to use the "main" FragmentManager from the activity, that hosts the container and the ViewPager, and not the one, that can be retrieved from fragments inside the ViewPager.
To do this, I've used:
this.getActivity().getSupportFragmentManager()
where this is an instance of Fragment.
Now if I navigate from an item in the ViewPager to some other fragment with "replace" transaction, upon returning I can see the ViewPager in the state that I've left.
More complete code sample looks like this:
FragmentManager fragmentManager = MyCurrentFragment.this.getActivity().getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.container, newFragment);
transaction.addToBackStack(null);
transaction.commit();

Categories

Resources