After deep dive into stackoverflow to find similar situation and no luck I decided to post my question. For my android app I have a settings (MySettings.java) PreferenceFragmentCompat which in turn on button click popups a list (MyList.java) ListFragment and after the item choose saves it and closes the list fragment. The fragments switch implemented as follow:
MySettings.java
#Override
public void onResume() {
super.onResume();
loadData();
}
private void loadData() {
// data load from SharedPreferences
}
...
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(android.R.id.content, MyList.class).addToBackStack().commit();
MyList.java
public void onListItemClick(ListView listView, View view, int position, long id) {
// data save into SharedPreferences
...
getFragmentManager().popBackStack();
}
So, after the list fragment is closed and it's returns back to settings fragment I want to show (load) previously saved item, namely I added onResume() (suggested in hier) method in turn triggers loading data function. But loading process is not triggered after .popBackStack()
What I'm doing wrong?
Is it generally possible to achieve this for settings concept with usage of fragments? Or there is better way? If so, please share your experience(s).
Any help would be appreciated!
onResume refers to the Activity and is only called when the Activity is resumed. As you use ft.replace the preference fragment is recreated, when calling popBackStack().
Thus you can put the loadData() in the onCreateView() method of your preference fragment.
Related
I am using one activity for tabactivity with viewpager and attached fragmentA.when i want to move fragmentB ,replaced fragmentB with present fragmentA.I need to find solution for onbackpress in FragmentB refresh fragmentA wihtout changing Backstack because i used nearly ten fragments in one tab.
Is there any method in fragment life cycle
When Fragment B over Fragment A, fragment A is onPause(). After backpress, fragment A is onResume(). So you can create a boolean to observe this change.
In Fragment A
private boolean isBackFromB;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
isBackFromB=false;
}
#Override
public void onPause() {
super.onPause();
isBackFromB = true;
}
#Override
public void onResume() {
super.onResume();
updateUI();
if (isBackFromB) //Do something
}
View pager does not always hold it's fragments in memory, so you have to handle 2 scenarios:
1 - fragment is recreated - just read data from wherever you hold them and show in ordinary way.
2 - fragment is modified from outside while still has been held by ViewPager, and as I understand this is what concerns you.
What you can do is to create this listener and then notify fragment that it was shown again and possibly should reload it's data.
More complex answer for communicating between different application's components is using some event bus like Otto.
override onBackPressed() method in Activity, add all fragment in backstack.
#Override
public void onBackPressed() {
FragmentManager manager =getFragmentManager();
int count = manager.getBackStackEntryCount();
if (count == 0) {
super.onBackPressed();
} else {
manager.popBackStack();
}
}
I've been researching this topic but so far no luck. Basically I'm replacing a fragment (A) with another one (B) using FragmentTransaction.replace. In this other fragment (B) I have a 'Cancel' button in the toolbar which when pressed pops back to the previous transaction (A) by calling getActivity().getSupportFragmentManager().popBackStackImmediate().
The problem is I need to update the Activity toolbar to display a different title when I'm showing fragment A and fragment B. I can't seem to find a method which gets called in fragment A whenever I go from A -> B -> A to inform me that it is visible again. The idea is to set the toolbar title in this callback which I cannot seem to find.
Can anyone point me in the right direction please?
Cheers.
Edit:
Method I call to replace the fragment with another one is as follows:
public static void replaceFragment(FragmentActivity parentActivity, int fragmentToReplaceId, Fragment withFragment, Integer enterAnim, Integer exitAnim)
{
FragmentManager fragmentManager;
FragmentTransaction transaction;
fragmentManager = parentActivity.getSupportFragmentManager();
transaction = fragmentManager.beginTransaction();
if ( (null != enterAnim) &&
(null != exitAnim) )
{
transaction.setCustomAnimations(enterAnim, exitAnim);
}
transaction.replace(fragmentToReplaceId, withFragment);
transaction.addToBackStack(null);
transaction.commit();
}
You can inform by overriding method onResume() in fragment and sending the message to activity or changing the Toolbar directly.
#Override
public void onResume() {
super.onResume();
((AppCompatActivity) getActivity()).getSupportActionBar().setTitle("Title");
}
In one activity, when replace A ---> B (A and B both are fragments), can use this call-back:
#Override
public void onAttachFragment(Fragment fragment) {
}
Solved by creating a simple static method in fragment A as follows:
public static void updateActivityTitle(FragmentActivity activity)
{
activity.setTitle(kActivityTitle);
}
Then I'm calling this method in fragment B as follows:
// cancel button has been pressed
private void cancel()
{
INV_CustomersFragment.updateActivityTitle(getActivity());
getActivity().getSupportFragmentManager().popBackStackImmediate();
}
Not ideal but it gets the job done. Any other solution involving a proper callback would be better
Update:
A better solution is the one described by #Rani at Fragment's onResume() not called when popped from backstack. This is more elegant and more maintainable, in fact I implemented this solution in my project. Compared to an equivalent solution for iOS this is still messy if you ask me, still seems the way to go.
Given an Activity that acts as a Home page (it never closes) that launches various fragments, how to know when the Activity is visible to the user?
From what I have observed, when I open a fragment the lifecycle for the Activity never changes, onPause() is not called. And when I close the fragment, onResume() is not called on my Activity.
Here is how I am starting my fragments, I am using this method and passing the fragment I want to launch to it.
public void addFragment(int containerId, Fragment fragment, boolean addToBackStack) {
// Check if the fragment has been added already. If so, then
// don't add the fragment.
Fragment temp = mFragmentManager.findFragmentByTag(fragment.getClass().getName());
if(temp != null && temp.isAdded()) {
return;
}
FragmentTransaction ft = mFragmentManager.beginTransaction();
ft.add(containerId, fragment, fragment.getClass().getName());
if(addToBackStack)
ft.addToBackStack(null);
ft.commit();
}
What is the methodology for indicating that my Activity is visible again? Thanks in advance!
in the oncreate method of your home activity, call
mFragmentManager.addOnBackStackChangedListener(this) ;
and then define
#Override
public void onBackStackChanged() {
int backStackCount = mFragmentManager.getBackStackEntryCount();
if(backStackCount == 0) {} //back to home screen
}
Your Activity is always Visible even if thousand Fragments are showing at the same time, for the sake of understanding Fragments are just Custom-Views, and the Fragment gives a helping hand in handling your View, so onPause() on your activity does not need to called when a Fragment dies or is born,just like inflating a View.
Just like Sir #Tim Mutton said, you need to check your BackStack to know if you are back, or you can use the ViewGroup method ViewGroup.indexOfChild(View child) - this method will an int of value getChildCount()-1 which means its on top of its fellow sibblings..
Hope it helps
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
EDIT
So it seems that my Fragment is retained in the FragmentManager which tries to reinitialize it. Still not sure why it isn't destroyed with the Activity. As for the loading message, this is displayed when the ListView does not have an adapter set. I however, set the adapter items in onCreate and onResume so I'm not sure why this loading screen is showing. Still open to any explanations of this behavior
Original
I'm playing around with fragments and noticed a weird error that is popping up when I change the orientation of the screen. This error should not be happening though because all the data is recreated in the onCreate when the screen orientation is changed. Also, the fragment onResume() is called twice after the rotation. Here's my steps for creating the error and how the debugger is hitting the functions.
Activity: onCreate()
Activity: onResume()
Fragment: onResume()
Rotate Screen
Activity: onCreate()
Activity: onResume()
Fragment: onResume() (items are null even though Activity.onResume() set them)
Fragment: onResume() (items are not null, why is this being called twice?)
After this last fragment onResume is hit, the tablet displays a "Loading..." message and icon. Why is the data not displayed in the list any more? My suspicions are that the onCreate is creating a second fragment. The first fragment looses its data because of the orientation destroying the views, the second fragment gets the data and the loading screen is the first fragment with no data items and the second fragment is hidden. I may be wrong. Why aren't the fragments all destroyed when the screen is rotated like the Activity? Please do not critique the code unless its to solve this specific issue. I'm not actually making an app, I'm experimenting with fragment functionality. Thanks!
Main Activity
private ArrayList<Object> items = new ArrayList<Object>();
private MyListFragment mylistFragment;
public MainActivity() {
items.add("Hello");
items.add("World");
items.add("Goodbye");
}
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
mylistFragment = new MyListFragment();
mylistFragment.setItems(items);
ft.add(R.id.container, mylistFragment);
ft.commit();
}
#Override
public void onResume() {
super.onResume();
mylistFragment.setItems(items);
mylistFragment.getListView().setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(MainActivity.this, ((TextView)view).getText(), Toast.LENGTH_LONG).show();
}
});
}
List Fragment
private List<Object> items = null;
private Boolean isSet = false;
#Override
public void onResume() {
super.onResume();
if( !isSet && items != null) {
setListAdapter(new ArrayAdapter<Object>(getActivity(), R.layout.item, items));
isSet = true;
}
}
public void setItems(List<Object> items) {
this.items = items;
if( this.isResumed() ) {
setListAdapter(new ArrayAdapter<Object>(getActivity(), R.layout.item, items));
isSet = true;
} else {
isSet = false;
}
}
The FragmentManager will automatically recreate the Fragment for me. However, the data inside them needs to be reinitialized. Therefore my code was creating a second fragment. The loading screen was because the first fragment no longer had its adapter of items and the second fragment was hidden. What a headache, I almost think the FragmentManager should destroy the Fragments so you can recreate them with the data they hold.
I believe this is why in the various demos via Diane and other google examples... they typically have some way of helping hte initialization via a static method... and setArguments.
IIRC, the fragment creation stuff will call the default constructor to create, but only knows about populating with appropriate data via setArguments. In the example on the android development blog... the id was being passed from the listactivity, to the details fragment. After setParams is called, the fragmentManager should be able to reanimate it.
Documentation seems to be sparse on the subject and I'd like to be doing things right, so if you guys have feedback, I'd appreciate it.
It seems that using FragmentActivity from the support library saves and restores instance automatically. Therefore, only do your fragment transactions if savedInstanceState is null.
For example, in your FragmentActivity's onCreate(), do the following:
if(savedInstanceState == null){
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, mFragment).commit(); //mFragment is your own defined fragment
}