Tracking current displayed fragments - android

I am adding several fragments in one fragment activity, is there a way to get which fragment is currently visible to the user after I have clicked my back button?
I have tried to do this by saving the tag of the current fragment in preferences and then when I update it when the back button is pressed. It's a but tedious but it works. However I feel it is better to use any methods provided by the system.
Unfortunately onResume and onStart don't seem to work for fragments in this scenario.
Is there a better way to handle this?

I also facing this situation in my work:
I do it like this:
subclass of fragment activity to manage the the push and pop
public class KFragmentActivity extends FragmentActivity{
private KFragment currentFragment;
//add new fragment
public void pushFragment(Class clz) {
//fragment = new fragement instance;
//hold the current instance
currentFragment = fragment;
}
//
public void popTopFragment() {
getSupportFragmentManager().popBackStackImmediate();
currentFragment = null;
int cnt = getSupportFragmentManager().getBackStackEntryCount();
if (cnt > 0) {
String name = getSupportFragmentManager().getBackStackEntryAt(cnt - 1).getName();
//hold current instance
currentFragment = (KFragment) getSupportFragmentManager().findFragmentByTag(name);
currentFragment.onBackFailed(null);
}
}
}

Related

How to save state in fragment

I have 4 button to replace fragment in activity [fragment A , fragment B , fragment C , fragment D] and then I replace fragment A to activity and I change value in fragment A after that I replace fragment B to fragment A and replace fragment C to fragment B . But I want to replace fragment A to fragment C . How to save state in fragment A.
Code when I commit fragment
private void beginFragmentTransaction(BaseFragment fragment) {
String tag = fragment.getClass().getName();
currentFragmentTag = tag;
boolean fragmentPopped = getChildFragmentManager().popBackStackImmediate(tag, 0);
if (!fragmentPopped) {
getChildFragmentManager().beginTransaction()
.replace(R.id.container, fragment, tag)
.addToBackStack(tag)
.commit();
}
}
Diagram to replace
fragment A -------> fragment B
fragment B -------> fragment C
fragment C -------> fragment A
PS. I don't want to use back button to back to fragment A , I want to replace fragment A and restore data in the first commit.
Instead of restoring the state during onCreate() you may choose to implement onRestoreInstanceState(), which the system calls after the onStart() method. The system calls onRestoreInstanceState() only if there is a saved state to restore, so you do not need to check whether the Bundle is null.
FYI : this is a sample code. Just for your reference.
public class MainFragment extends Fragment {
private String title;
private double rating;
private int year;
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putString(TITLE, "Gladiator");
savedInstanceState.putDouble(RATING, 8.5);
savedInstanceState.putInt(YEAR, 2000);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
title = savedInstanceState.getString(TITLE);
rating = savedInstanceState.getDouble(RATING);
year = savedInstanceState.getInt(YEAR);
}
}
FYI : This really a good thread check this also Once for all, how to correctly save instance state of Fragments in back stack?
If you want to save the state of previous tabs and don't want to refresh/recreate view use this code and change the value according to the tabs limit
ViewPager mViewPager = (ViewPager)findViewById(R.id.pager);
mViewPager.setOffscreenPageLimit(2);
you can show and hide fragments for saving the states,
or use the navigation component latest version

Preload some fragment when the app starts

I have an Android application with a navigation drawer. My problem is that some fragment takes few second to load (parser, Map API). I would like to load all my fragment when the app starts.
I'm not sure if it is possible or a good way to do it, but I was thinking of create an instance of each of my fragments in the onCreate method of the main activity. Then, when the user select a fragment in the navigation drawer, I use the existing instance instead of creating a new one.
The problem is that it does not prevent lag the first time I show a specific fragment. In my opinion, the reason is that the fragment constructor does not do a lot of operation.
After searching the web, I can't find an elegant way to "preload" fragment when the application starts (and not when the user select an item in the drawer).
Some post talks about AsyncTask, but it looks like MapFragment operation can't be executed except in the main thread (I got an exception when I try: java.lang.IllegalStateException: Not on the main thread).
here is what I've tried so far:
mFragments = new Fragment[BasicFragment.FRAGMENT_NUMBER];
mFragments[BasicFragment.HOMEFRAGMENT_ID] = new HomeFragment();
mFragments[BasicFragment.CAFEFRAGMENT_ID] = new CafeFragment();
mFragments[BasicFragment.SERVICEFRAGMENT_ID] = new ServiceFragment();
mFragments[BasicFragment.GOOGLEMAPFRAGMENT_ID] = new GoogleMapFragment();
When an item is selected in the nav drawer:
private void selectItem(int position) {
Fragment fragment = mFragments[position];
// here, I check if the fragment is null and instanciate it if needed
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.replace(R.id.content_frame, fragment);
ft.commit();
mDrawerList.setItemChecked(position,true);
mDrawerLayout.closeDrawer(mDrawerList);
}
I also tried this solution; it allows to prevent a fragment from being loaded twice (or more), but it does not prevent my app from lag the first time I show it. That's why I try to load all fragments when the application starts (using a splash-screen or something) in order to prevent further lags.
Thanks for your help / suggestion.
You can put your fragments in ViewPager. It preloads 2 pages(fragments) by default. Also you can increase the number of preloaded pages(fragments)
mViewPager.setOffscreenPageLimit(int numberOfPreloadedPages);
However, you will need to rewrite your showFragment method and rewrite back stack logic.
One thing you can do is load the resources in a UI-less fragment by returning null in in Fragment#onCreateView(). You can also call Fragment#setRetainInstance(true) in order to prevent the fragment from being destroyed.
This can be added to the FragmentManager in Activity#onCreate(). From there, Fragments that you add can hook in to this resource fragment to get the resources they need.
So something like this:
public class ResourceFragment extends Fragment {
public static final String TAG = "resourceFragment";
private Bitmap mExtremelyLargeBitmap = null;
#Override
public View onCreateView(ViewInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return null;
}
#Override
public void onStart() {
super.onStart();
new BitmapLoader().execute();
}
public Bitmap getExtremelyLargeBitmap() {
return mExtremelyLargeBitmap;
}
private class BitmapLoader extends AsyncTask<Void, Void, Bitmap> {
#Override
protected Bitmap doInBackground(Void... params) {
return decodeBitmapMethod();
}
#Override
protected void onPostExecute(Bitmap result) {
mExtremelyLargeBitmap = result;
}
}
}
Add it to the fragment manager in the Activity first thing. Then, whenever you load your other Fragments, they merely have to get the resource fragment from the fragment manager like so:
public class FakeFragment extends Fragment {
#Override
public View onCreateView(ViewInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final ResourceFragment resFragment = getFragmentManager().findFragmentByTag(ResourceFragment.TAG);
Bitmap largeBitmap = resFragment.getBitmap();
if (largeBitmap != null) {
// Do something with it.
}
}
}
You will probably have to make a "register/unregister" listener set up because you will still need to wait until the resources are loaded, but you can start loading resources as soon as possible without creating a bunch of fragments at first.
To preload fragments, attach() can be used. So in OP's case it will be:
ft.attach(fragment).commit();
Make sure to store the fragment somewhere and use that one the next time ft.replace() is called.

Multiple backstacks are added to the fragment transaction

In my android application, a fragment will be added to the activity by a certain action (for example, the action bar menu).
This is the code I add the fragment:
case R.id.action_add_box:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.place, BoxEditFragment.newInstance(null, null));
ft.addToBackStack(null);
ft.commit();
break;
Now once the user hit the action menu with id action_add_box two times, then he have to hit the back two times to close the fragment which is not expected.
Is it possible to avoid this?
For example, once user hit the action menu, do nothing if the fragment have been already visible to the user?
And one more question, there are some EditTexts in the fragment, once user complete, I will submit the data and close the fragment, however user may need to open the fragment again, and I want to keep the value of the EditText as last entered by user. Now I save the values when the fragment are detached and reset the value when created using the savedInstanceState.
Also create a new instance of the fragment for each action command is a waste of memory, I wonder if I can use only one fragment instance, then I may not need to save/reset the values manually?
you can use singleton parttern to keep one instance of fragment for eg:
public class MyFragment extends Fragment{
public static MyFragment oneInstance = null ;
private MyFragment(){
super();
}
public static MyFragment getInstance(){
if (oneInstance == null ){
synchronized (MyFragment.class){
if ( oneInstance == null ){
oneInstance = new MyFragment();
}
}
return oneInstance ;
}
}
the above code is also thread safe
MyFragment frag= (MyFragment )getFragmentManager().findFragmentById(R.id.your_fragment_layout);
if(frag == null){
// fragment is not visible
}else{
// fragment is visible
}

Fragments being inflated with old data, when going back to an activity that was stopped

Activity A has fragments. When it starts an intent to activity B, then when B.finish(), A executes onCreate() again.
But this time, even though A.onCreate() has a new PacksPagerAdapter and a new ViewPager, the fragments are shown with old data.
I can see that that onCreateView() is executed for each fragment, but it still has the old arguments since the static newInstance() wasn't called. Arguments are created when FragmentPagerAdapter's getItem(position) is called.
Here's how it's implemented -
public class PackActivity extends Activity {
...
PacksPagerAdapter mPacksPagerAdapter;
ViewPager mViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPacksPagerAdapter = new MyPagerAdapter(getFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mPacksPagerAdapter);
mViewPager.setOffscreenPageLimit(PACK_PAGES - 1);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
actionBar.setTitle(...);
}
});
}
...
}
public class MyPagerAdapter extends FragmentPagerAdapter {
...
#Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
// Return a PlaceholderFragment (defined as a static inner class
// below).
return PackFragment.newInstance(myData);
}
...
}
public class PackFragment extends Fragment {
...
public static PackFragment newInstance(PackInfo pack) {
PackFragment fragment = new PackFragment();
Bundle bdl = new Bundle(2);
bdl.putSerializable(EXTRA_PACK, pack);
bdl.putInt(EXTRA_PACK_POSITION, pack.packNumber);
fragment.setArguments(bdl);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View packView = inflater.inflate(R.layout.pack_fragment, container, false);
// this has the old argument, since newInstance(...) wasn't called
PackInfo pack = (PackInfo) getArguments().getSerializable(EXTRA_PACK);
...
return packView;
}
...
}
Any idea why new fragments aren't being instantiated on the 2nd creation of activity A?
Update - Activity-Fragments lifecycle
The answer is, as Tal said, is that activity is being restored, so old fragments are being reattached instead of creating new ones.
After spending a lot of time studying this, I've found that there are typically 3 scenarios for activity-fragments lifecycle. Here are the scenarios, the lifecycle and how to reload fragment's old data:
New activity
Lifecycle: onCreate() ==> onResume() ==> Fragments are created and attached
No need to reload.
Restored activity
Lifecycle: onCreate() ==> Fragments inflated with old data and reattached ==> onResume()
In onResume(), remove all fragments and create new ones, either manually or automatically using an adapter -
// Remove all fragments
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
for (int i = BuildConfig.PACK_PAGES - 1; i >= 0; i--) {
Fragment f = findFragmentByPosition(i);
if (f != null)
fragmentTransaction.remove(f);
}
fragmentTransaction.commit();
// This will force recreation of all fragments
viewPager.setAdapter(packsPagerAdapter);
Resumed activity
Lifecycle: onResume()
Same as activity restore, remove old fragments and recreate.
Note: Some OS versions always restores activity, even when opening a SettingsActivity for a few seconds, and other (older) versions will always resume.
my answer will be better if you'll post the fragment transaction you commit in activity A.
not sure that you know it or not - if Activity A is re-created when it pop back from back stack - it means that it restored from previous instance state.
in that case, you should not perform the transaction again, because - it already happens automatically via the super.onCreate() Activity method. in fact, if you'll perform the fragment trasaction in that case - you'll cause the same fragment to be added twice (2 instances)
you can know if currently onCreate() been called from restoring instance state by checking if savedInstanceState parameter is null or not.
my assumption is that you are not checking savedInstanceState, and performing the transaction anyway.
if I'm right, then the following change should fix the fragment duplication problem:
instead of:
public static class ActivityA extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
performFragmentTransaction();
}
write:
public static class ActivityA extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
performFragmentTransaction();
}
}
more info at - http://developer.android.com/guide/components/fragments.html
When the user moves away from activity A to B, the FragmentPagerAdapter detaches the fragments in A from user interface and does not destroy the instances so that they can be reused when user returns back to A latter.
I'm not sure why onCreate of A is called after B finishes. If you don't want the old fragments to be attached to the user interface, instead you want them to be recreated, then you need to explicitly destroy them.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPacksPagerAdapter = new MyPagerAdapter(fragmentManager);
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.pager);
// Remove the fragments if they already exist.
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction currentTransaction = fragmentManager.beginTransaction();
int count = mPacksPagerAdapter.getCount();
for(int position = 0; position < count; position++) {
long itemId = mPacksPagerAdapter.getItemId(position);
// fragment name format maintained by adpater: "android:switcher:" + viewId + ":" + id
String name = "android:switcher:" + mViewPager.getId() + ":" + itemId;
Fragment fragment = fragmentManager.findFragmentByTag(name);
if(fragment != null) {
// if fragment instance exists, destroy it.
currentTransaction.remove(fragment);
}
}
// commit all the operations with state loss.
currentTransaction.commitAllowingStateLoss();
// immediately execute the operations
fragmentManager.executePendingTransactions();
// Rest of your code follows here.
....
}
i think you should add your fragment to your view and commit.
write something like this in your "onCreate".
fragmentTransaction.add(R.id.container, YOUR_FRAGMENT);
fragmentTransaction.commit();
or replace the old one.
This is the advertised behavior: Your fragments are being restored.
Actually, it should also be noted that it is merely chance that is causing Activity A to be recreated. It could be possible that Activity A still exists after B finishes which will not lead to execution of onCreate of A again.
Why newInstance is not being called?
This happens because the fragments are actually in the FragmentManager of the killed activity. Therefore, they are restored when the activity is recreated. The tags of the fragments are usually:
"android:switcher:" + your_pager_id + ":" + your_fragment_position.

Android Fragment, going back without recreating/reloading Fragment

I've seen quite a few questions on SO about Fragments and I still can't seem to figure out if what I want to do is possible, and more so if my design pattern is just flawed and I need to re-work the entire process. Basically, like most questions that have been asked, I have an ActionBar with NavigationTabs (using ActionBarSherlock), then within each Tab there is a FragementActivity and then the FragmentActivities push new Fragments when a row is selected (I'm trying to re-create an iOS Project in Android and it's just a basic Navigation based app with some tabs that can drill down into specific information). When I click the back button on the phone the previous Fragment is loaded but the Fragment re-creates itself (so the WebServices are called again for each view) and this isn't needed since the information won't change in a previous view when going backwards. So basically what I want to figure out is how do I setup my Fragments so that when I push the back button on the phone, the previous Fragment is just pulled up with the previous items already created. Below is my current code :
//This is from my FragmentActivity Class that contains the ActionBar and Tab Selection Control
#Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
int selectedTab = tab.getPosition();
if (selectedTab == 0) {
SalesMainScreen salesScreen = new SalesMainScreen();
ft.replace(R.id.content, salesScreen);
}
else if (selectedTab == 1) {
ClientMainScreen clientScreen = new ClientMainScreen();
ft.replace(R.id.content, clientScreen);
}.....
//This is within the ClientMainScreen Fragment Class, which handles moving to the Detail Fragment
row.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//Do something if Row is clicked
try{
String selectedClientName = clientObject.getString("ClientName");
String selectedClientID = clientObject.getString("ClientID");
String selectedValue = clientObject.getString("ClientValue");
transaction = getFragmentManager().beginTransaction();
ClientDetailScreen detailScreen = new ClientDetailScreen();
detailScreen.clientID = selectedClientID;
detailScreen.clientName = selectedClientName;
detailScreen.clientValue = selectedValue;
int currentID = ((ViewGroup)getView().getParent()).getId();
transaction.replace(currentID,detailScreen);
transaction.addToBackStack(null);
transaction.commit();
}
catch (Exception e) {
e.printStackTrace();
}
}
});....
//And then this is the Client Detail Fragment, with the method being called to Call the Web Service and create thew (since what is displayed on this screen is dependent on what is found in the Web Service
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup group, Bundle saved) {
return inflater.inflate(R.layout.clientdetailscreen, group, false);
}
#Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//Setup Preferences File Link
this.preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
//initialize the table object
mainTable = (TableLayout)getActivity().findViewById(R.id.mainTable);
//setup the detail table
setupRelatedClientSection();
}
The Client Detail Screen can then drill down one more time, using the same method as the Client Main Screen but when I go back from that new screen to the Detail Screen the seuptRelatedClientSection() method is called again and so the entire Fragment is rebuilt when really I just want to pull up a saved version of that screen. Is this possible with my current setup, or did I approach this the wrong way?
Try using fragementTransaction.add instead of replace
I believe that you are looking for show() and hide().
I think you can still add them to the backstack.
transaction.hide(currentFragment);
transaction.show(detailScreen);
transaction.addToBackStack(null);
transaction.commit();
I didnt have my code to look at but i believe this is how it would go... Try it out unless someone else has a better way.
I have not tried the backstack with show() hide() but i believe that it takes the changes that are made before the transactions commit and will undo them if the back button is pressed. Please get back to me on this cause i am interested to know.
You also have to make sure that the detail fragment is created before you call this. Since it is based on the click of someitem then you should probably create the details fragment every time you click to make sure the correct details fragment is created.
I'm posting this answer for people who may refer this question in future.
Following code will demonstrate how to open FragmentB from FragmentA and going back to FragmentA from FragmentB (without refreshing FragmentA) by pressing back button.
public class FragmentA extends Fragment{
...
void openFragmentB(){
FragmentManager fragmentManager =
getActivity().getSupportFragmentManager();
FragmentB fragmentB = FragmentB.newInstance();
if (fragmentB.isAdded()) {
return;
} else {
fragmentManager.
beginTransaction().
add(R.id.mainContainer,fragmentB).
addToBackStack(FragmentB.TAG).
commit();
}
}
}
public class FragmentB extends Fragment{
public static final String TAG =
FragmentB.class.getSimpleName();
...
public static FragmentB newInstance(){
FragmentB fragmentB = new FragmentB();
return fragmentB;
}
#Override
public void onResume() {
super.onResume();
// add this piece of code in onResume method
this.getView().setFocusableInTouchMode(true);
this.getView().requestFocus();
}
}
In your MainActivity override onBackPressed()
class MainActivity extends Activity{
...
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
}
You're right, there has been a number of previous questions / documentation on the topic ;)
The documentation on Fragments, specifically the section about Transactions and Saving State, will guide you to the answer.
http://developer.android.com/guide/components/fragments.html#Transactions
http://developer.android.com/guide/components/activities.html#SavingActivityState
Android - Fragment onActivityResult avoid reloading
Fragments can have support for onSaveInstanceState but not onRestoreInstanceState, so if you want to save a reference to the table views, save them to the Bundle and you can access the saved view in your onActivityCreated method. You could also use the Fragments back stack.
This guide/tutorial has very detailed instructions/examples on the back stack and retaining fragment state.
Good luck

Categories

Resources