In my project context, I have a Button b in a Fragment f(1) in an Activity a.
Fragment f(x) is an instance of F where content depends of argument x
I need to replace the current instance f(1) by an instance f(2) on b click event:
From Activity a:
private void setFragment(int x) {
Bundle args = new Bundle();
args.putInt("x", x);
F f = new F();
f.setArguments(args);
f.setListener(new F.Listener() {
public void onButtonClick(int x) {
setFragment(x);
}
});
getSupportFragmentManager()
.beginTransaction()
.replace(ID, f)
.commit();
}
From Fragment f:
b.setOnClickListener(new View.onClickListener() {
public void onClick(View view) {
listener.onButtonClick(x + 1);
}
});
My problem is:
An Exception is throw on b click event only if a configuration state change occurs:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
Please, what is my error? I read many posts on this Exception but I don't found any solution
Edit: I just make a test without AsyncTask, see the code:
Try to rotate the screen and push the button
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
if (state == null) {
setFragment(1);
}
}
private void setFragment(int id) {
Bundle args = new Bundle();
args.putInt("id", id);
MyFragment myFragment = new MyFragment();
myFragment.setArguments(args);
myFragment.setListener(new MyFragment.Listener() {
#Override
public void onClick(int id) {
setFragment(id);
}
});
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment, myFragment)
.commit();
}
public static class MyFragment extends Fragment{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup view, Bundle state) {
return new Button(getActivity()) {
{
setText(String.valueOf(getArguments().getInt("id")));
setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
listener.onClick(getArguments().getInt("id") + 1);
}
});
}
};
}
private static interface Listener {
public void onClick(int id);
}
private Listener listener;
public void setListener(Listener listener) {
this.listener = listener;
}
}
}
The problem is the way you are setting the listener.
You are setting the listener, then you rotate your device from landscape to portrait. What happens after the rotation is:
Android create a brand new instance of MainActivity.
FragmentManager create a new instance of MyFragment internally and re-adds it automatically to the activity as it was before orientation change.
If you click on the button, the listener will be called. However, the listener is the listener of the previous activity (before rotation) which has been destroyed.
So not only you have a Memory Leak (the old activity can not be garbage collected, because it's referenced from here) but you also get this error.
How to do it correctly:
Well, the problem is NOT only setRetainInstanceState() you have not understood the Android Fragments lifecycle correctly. As mentioned above, Fragments are controlled by the FragmentManager (FragmentTransaction). So, yes everytime you rotate your screen a new Fragment instance will be created, but FragmentManager will attach them automatically for you (it's a little bit strange, but thats how Fragment works)
I would recommend to use an EventBus. The fragment will fire an Event onClick() and the activity will receive this event since it's subscribed. I recomment GreenDao EventBus.
Otherwise you can have a look at the official docs, but from my point of view they are teaching not a good solution, because your fragment and activity are hardly connected (not modular). They say you should use onAttach() like you can see in the sample from the documentation: http://developer.android.com/guide/components/fragments.html#EventCallbacks
Btw. a similar problem can occur if you are not using Fragment arguments for "passing" data. For more details read this blog: http://hannesdorfmann.com/android/fragmentargs/
Related
I've been refactoring a previous project to use fragments instead of creating separate activities as a school assignment. I've been trying to find out where this error is for close to an hour now and no luck. I get context missing when I add this line canvas.setColor(color, position);
Here's the Main activity:
public class MainActivity extends AppCompatActivity implements PaletteFragment.SpinnerSelectedInterface {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PaletteFragment palette = PaletteFragment.newInstance();
Bundle bundle = new Bundle();
bundle.putStringArray(KeyData.PASS_COLOR, getResources().getStringArray(R.array.colors));
bundle.putStringArray(KeyData.PASS_POSITION, getResources().getStringArray(R.array.colorNames));
palette.setArguments(bundle);
getSupportFragmentManager().beginTransaction().add(R.id.palette_fragment, palette).commit();
}
#Override
public void setCanvasColor(String color, int position) {
CanvasFragment canvas = CanvasFragment.newInstance(null);
getSupportFragmentManager().beginTransaction().add(R.id.canvas_fragment, canvas).addToBackStack(null).commit();
canvas.setColor(color, position);
}
}
And here's the fragment:
public class CanvasFragment extends Fragment {
private TextView displayColor;
private View background;
public CanvasFragment() {
// Required empty public constructor
}
public static CanvasFragment newInstance(Bundle bundle) {
CanvasFragment fragment = new CanvasFragment();
fragment.setArguments(bundle);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_canvas, container, false);
displayColor = (TextView) v.findViewById(R.id.displayColor);
background = (View) v.findViewById(R.id.canvas_fragment);
return v;
}
public void setColor(String color, int position){
String[] names = getResources().getStringArray(R.array.colorNames);
displayColor.setText(names[position]);
background.setBackgroundColor(Color.parseColor(color));
}
I've tried override onAttach and onDetach on the canvas fragment and adding a message listener, but still get the error as well. Would appreciate anything that could steer me in the right direction.
Error
java.lang.IllegalStateException: Fragment CanvasFragment{69925d5 (240edefb-318c-4983-bd15-cf45142e849a) id=0x7f080047} not attached to a context.
When you call
getSupportFragmentManager()
.beginTransaction()
.add(R.id.canvas_fragment, canvas)
.addToBackStack(null)
.commit();
You're using commit(). As per its Javadoc:
Schedules a commit of this transaction. The commit does not happen immediately; it will be scheduled as work on the main thread to be done the next time that thread is ready.
So when you immediately call canvas.setColor(color, position) directly afterwards, the Fragment is not attached and it doesn't yet have a Context associated with it.
If you want the Fragment to be immediately added, you want to use commitNow(), which forces the transaction to happen immediately. This ensures that it'll be done before your setColor method is called:
getSupportFragmentManager()
.beginTransaction()
.add(R.id.canvas_fragment, canvas)
.addToBackStack(null)
.commitNow();
I'm pretty new to Android.
This is my scenario: I have a simple app with 3 tabs. In each tab i want to use one or more fragments. This is the situation:
Tab 1:
Fragment A
Tab 2:
Fragment B
Fragment C
Fragment D
Tab 3:
Fragment E
Fragment F
In "Tab 1" I have no issue. All works pretty good. Issues arise when I need to move in "Tab 2" and "Tab 3".
In Tab 2 I have to propagate some parameters from "Fragment B" to "Fragment C" and from "Fragment C" to "Fragment D".
Then it can happen that when user clicks on some button in "Fragment D" I have to pass to "Tab 3" and I have to propagate some parameters from "Fragment D" to "Fragment E".
In my main Activity for Tab handling I'm using these components:
android.support.design.widget.TabLayout
android.support.v4.view.ViewPager
android.support.v4.app.FragmentStatePagerAdapter (I created a custom
class)
android.support.design.widget.TabLayout.OnTabSelectedListener (I created a custom class)
My very simple FragmentStatePagerAdapter extension is:
public class MyOwnPageAdapter extends FragmentStatePagerAdapter {
private int numeroTab;
public MyOwnPageAdapter(FragmentManager fm, int numeroTab) {
super(fm);
this.numeroTab = numeroTab;
}
#Override
public Fragment getItem(int position) {
switch (position){
case 0:
return new FragmentA() ;
case 1:
return new FragmentB() ;
case 2:
return new FragmentC() ;
default:
return null;
}
}
#Override
public int getCount() {
return numeroTab;
}
}
My very simple TabLayout.OnTabSelectedListener extension is:
public class TabSelectedListener implements TabLayout.OnTabSelectedListener {
private ViewPager viewPager;
public TabSelectedListener(ViewPager viewPager){
this.viewPager = viewPager;
}
#Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
}
I'm able in switching fragments inside tabs that is in Tab 2 i can switch from Fragment B to Fragment C and so on. I'm having issues in passing parameters between fragments and above all from Fragment D in Tab 2 to Fragment E in Tab 3
In my Fragments implementation byt using the android.support.v4.app.FragmentManager I can add and remove views (e.g. fragments) by doing something like this:
mFragmentManager.beginTransaction().add(rootView.getId(),mListaEdificiFragment, "BUILDS").addToBackStack(null).commit();
The problem is the param propagation that since the FragmentStatePagerAdapter seems to cache views it happens that the fragment constructor is called but the onCreate and onCreateView are no more called so I can't handle the propagated parameters.
Is there any solution to this? Or am I simply wrong in my navigation pattern? I would like to avoid to collapse Fragment B,Fragment C and Fragment D in one "big view" where to hide some section (the same for Fragment E e Fragment F)
Any suggestion is more then welcome
Angelo
One simple solution to transfer a variable value from one fragment to another is shared preferences (can also be used to transfer values from one activity to another too). Shared preference will save data against variables that will persist across all the activities and fragments in an android app.
Now in your case, lets assume you want to transfer a value name = angelo from your fragment A to fragment B. In your fragment A, write this code:
Button updateName = findViewById(R.id.btnupdateTeamName);
updateTeamName .setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString("name", "angelo");
editor.commit();
}
});
When executed, the above code will update a value name with angelo in shared preferences. This will be available throughout your app.
For more info about shared preference, check out this official document.
I write my Fragment like this for passing data to it.
public class MyFragment extends Fragment {
private static String ARG_PARAM1 = "data";
private String data;
public MyFragment() {
// Required empty public constructor
}
public static MyFragment newInstance(String data) {
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, data);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
data = getArguments().getString(ARG_PARAM1);
}
}
}
Now data can be passed to the Fragment by calling MyFragment.newInstance("Hello"). I hope this helps.
I have faced a similar issue in my project.
In my case, I have viewpager and each tab has multiple fragment.
So one of the simple solutions is to use LiveData and ViewModel.
In your Tab2:
Fragment B
Fragment C
Fragment D
TabTwoViewModel (with live data)
In mutable Live data observer this live data to Fragment B, C, and D.
When you update live data object, Live data notify automatically all fragment.
Finally I got a solution.
Since the main problem is the fragments communication, I followed the official documentation
Let's suppose I have Fragment A with list of articles and Fragment B where to see the selected article detail, in my Fragment A i wrote:
public class FragmentA extends Fragment {
private OnArticleSelectionListener mCallback;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
}
}
#Override
public void onStart() {
super.onStart();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.activityedifici, container, false);
return rootView;
}
public interface OnArticleSelectionListener {
void onArticleSelection(String articleId);
}
#Override
public void onDetach() {
super.onDetach();
mCallback = null;
}
public void setOnArticleSelectionListener(OnArticleSelectionListener mCallback) {
this.mCallback = mCallback;
}
}
As you can see I declared the following interface
public interface OnArticleSelectionListener {
void onArticleSelection(String articleId);
}
This is the article selection listener.
In my Main Activity I wrote the following:
public class MainActivity implements FragmentA.OnArticleSelectionListener{
//All my own stuffs
#Override
public void onAttachFragment(Fragment fragment) {
if (fragment instanceof FragmentA){
FragmentA ef = (FragmentA)fragment;
ef.setOnArticleSelectionListener(this);
}
}
#Override
public void onArticleSelection(String articleId) {
if( getSupportFragmentManager().findFragmentByTag(TAG_ARTICLE_DETAIL) != null ){
//FragmentB is the article detail and it has already been created and cached
FragmentB dcf = (FragmentB)getSupportFragmentManager().findFragmentByTag(TAG_ARTICLE_DETAIL);
dcf.updateArticleDetail( articleId );
}else{
//FragmentB is the article detail and it has never been created I create and replace the container with this new fragment
FragmentB dcf = new FragmentB();
//Parameter propagation
Bundle args = new Bundle();
args.putString(FragmentB.ARG_ARTICLE_ID, articleId);
dcf.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.container_articles, dcf, TAG_ARTICLE_DETAIL);
transaction.addToBackStack(null);
transaction.commit();
}
}
}
In this way I'm able in intercepting events in FragmentA and propagate them to the FragmentB; when I need to open a Tab all remains the same and finally (after transaction.commit() or the dcf.updateArticleDetail( articleId )) I do the following tabLayout.getTabAt(2).select(); and the third tab (tab index starts from 0) is open and the Detail is showed.
I hope this can be useful
Angelo
I have a Fragment FR1 that contains several Nested Fragments; FRa, FRb, FRc. These Nested Fragments are changed by pressing Buttons on FR1's layout. Each of the Nested Fragments have several input fields within them; which include things like EditTexts, NumberPickers, and Spinners. When my user goes through and fills in all the values for the Nested Fragments, FR1 (the parent fragment) has a submit button.
How can I then, retrieve my values from my Nested Fragments and bring them into FR1.
All Views are declared and programmatically handled within each Nested Fragment.
The parent Fragment, FR1 handles the transaction of the Nested Fragments.
I hope this question is clear enough and I am not sure if code is necessary to post but if someone feels otherwise I can do so.
EDIT 1:
Here is how I add my Nested Fragments:
tempRangeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getChildFragmentManager().beginTransaction()
.add(R.id.settings_fragment_tertiary_nest, tempFrag)
.commit();
}
});
scheduleButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getChildFragmentManager().beginTransaction()
.add(R.id.settings_fragment_tertiary_nest, scheduleFrag)
.commit();
}
});
alertsButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getChildFragmentManager().beginTransaction()
.add(R.id.settings_fragment_tertiary_nest, alertsFrag)
.commit();
}
});
submitProfile.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
constructNewProfile();
}
});
where my constructNewProfile() method needs the values from my Nested Fragments.
public Fragment tempFrag = fragment_profile_settings_temperature
.newInstance();
public Fragment scheduleFrag= fragment_profile_settings_schedules
.newInstance();
public Fragment alertsFrag = fragment_profile_settings_alerts
.newInstance();
The above refers to the fields of the parent fragment; and how they are initially instantiated.
The best way is use an interface:
Declare an interface in the nest fragment
// Container Activity or Fragment must implement this interface
public interface OnPlayerSelectionSetListener
{
public void onPlayerSelectionSet(List<Player> players_ist);
}
Attach the interface to parent fragment
// In the child fragment.
public void onAttachToParentFragment(Fragment fragment)
{
try
{
mOnPlayerSelectionSetListener = (OnPlayerSelectionSetListener)fragment;
}
catch (ClassCastException e)
{
throw new ClassCastException(
fragment.toString() + " must implement OnPlayerSelectionSetListener");
}
}
#Override
public void onCreate(Bundle savedInstanceState)
{
Log.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
onAttachToParentFragment(getParentFragment());
// ...
}
Call the listener on button click.
// In the child fragment.
#Override
public void onClick(View v)
{
switch (v.getId())
{
case R.id.tv_submit:
if (mOnPlayerSelectionSetListener != null)
{
mOnPlayerSelectionSetListener.onPlayerSelectionSet(selectedPlayers);
}
break;
}
}
Have your parent fragment implement the interface.
public class Fragment_Parent extends Fragment implements Nested_Fragment.OnPlayerSelectionSetListener
{
// ...
#Override
public void onPlayerSelectionSet(final List<Player> players_list)
{
FragmentManager fragmentManager = getChildFragmentManager();
SomeOtherNestFrag someOtherNestFrag = (SomeOtherNestFrag)fragmentManager.findFragmentByTag("Some fragment tag");
//Tag of your fragment which you should use when you add
if(someOtherNestFrag != null)
{
// your some other frag need to provide some data back based on views.
SomeData somedata = someOtherNestFrag.getSomeData();
// it can be a string, or int, or some custom java object.
}
}
}
Add Tag when you do fragment transaction so you can look it up afterward to call its method. FragmentTransaction
This is the proper way to handle communication between fragment and nest fragment, it's almost the same for activity and fragment.
http://developer.android.com/guide/components/fragments.html#EventCallbacks
There is actually another official way, it's using activity result, but this one is good enough and common.
Instead of using interface, you can call the child fragment through below:
( (YourFragmentName) getParentFragment() ).yourMethodName();
The best way to pass data between fragments is using Interface. Here's what you need to do:
In you nested fragment:
public interface OnDataPass {
public void OnDataPass(int i);
}
OnDataPass dataPasser;
#Override
public void onAttach(Activity a) {
super.onAttach(a);
dataPasser = (OnDataPass) a;
}
public void passData(int i) {
dataPasser.OnDataPass(i);
}
In your parent fragment:
public class Fragment_Parent extends Fragment implements OnDataPass {
...
#Override
public void OnDataPass(int i) {
this.input = i;
}
btnOk.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Fragment fragment = getSupportFragmentManager().findFragmentByTag("0");
((Fragment_Fr1) fragment).passData();
}
}
}
You can use share data between fragments.
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, item -> {
// Update the UI.
});
}
}
More Info ViewModel Architecture
You can use getChildFragmentManager() and find nested fragments, get them and run some methods to retrieve input values
Check for instanceOf before getting parent fragment which is better:
if (getParentFragment() instanceof ParentFragmentName) {
getParentFragment().Your_parent_fragment_method();
}
Passing data between fragments can be done with FragmentManager. Starting with Fragment 1.3.0-alpha04, we can use setFragmentResultListener() and setFragmentResult() API to share data between fragments.
Official Documentation
Too late to ans bt i can suggest create EditText object in child fragment
EditText tx;
in Oncreateview Initialize it. then create another class for bridge like
public class bridge{
public static EditText text = null;
}
Now in parent fragment get its refrence.
EditText childedtx = bridge.text;
now on click method get value
onclick(view v){
childedtx.getText().tostring();
}
Tested in my project and its work like charm.
I am attempting to create an app which has a Master/Detail flow using Fragments. Selecting an item will open a detail fragment which may then which to "open" another fragment and add it to the back stack.
I have renamed classes to help illustrate what they do.
public class ListOfDetails extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
//Callback method indicating that an item with the given ID was selected.
public void onItemSelected(String id) {
// Performing logic to determine what fragment to start omitted
if (ifTwoPanes()) {
Fragment fragment = new DetailFragmentType1();
getSupportFragmentManager().beginTransaction().replace(R.id.aContainer, fragment).commit();
} else {
Intent newIntent = new Intent(this, SinglePaneFragmentWrapper.class);
newIntent.putExtra("id", id);
startActivity(newIntent);
}
}
// My attempt at making it possible to change displayed fragment from within fragments
public void changeDetailFragment(Fragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(null);
transaction.replace(R.id.aContainer, fragment);
transaction.commit();
}
}
An example of one of the detail fragments. There are many different Fragments that may be created in different circumstances.
public class DetailFragmentType1 extends Fragment {
private ListOfDetails parent;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Activity a = getActivity();
if (a instanceof ListOfDetails) {
parent = (ListOfDetails) a;
}
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button aButton = (Button) getActivity().findViewById(R.id.aButton);
aButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
parent.changeDetailFragment(new SubDetailFragment());
}
});
}
}
When on phone, a wrapper activity is used to hold the fragment
public class SinglePaneFragmentWrapper extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Duplicate logic must be performed to start fragment
// Performing logic to determine what fragment to start omitted
String id = getIntent().getStringExtra("id");
if(id == "DetailFragmentType1") {
Fragment fragment = new DetailFragmentType1();
getSupportFragmentManager().beginTransaction().replace(R.id.aContainer, fragment).commit();
} else {
...
}
}
}
What is the proper way to change the fragment that is open in the detail pane in this circumstance? My method feels like a hack when using two panes and doesn't even work when using only one pane because getParent() from SinglePaneFragmentWrapper returns null, making me unable to call parent.changeDetailFragment().
This is a complicated question, hopefully I explained it well. Let me know if I missed something. Thanks
There are lots of opinions around this and lots of ways of doing it. I think in this case the problem is "who is responsible for changing the fragment?" on the surface it seems that a listener on the button is the obvious place, but then the fragment shouldn't know what it is hosted in (a symptom of that is getting an undesirable result like null from getParent()).
In your case I would suggest you implement a "listener" interface in the parent and "notify" from the fragment.. when the parent is notified, it changes the fragment. This way the fragment is not changing itself (so doesn't need to know how).. so.. for your case..
Add a new interface:
public interface FragmentChangeListener {
void onFragmentChangeRequested(Fragment newFragment);
}
Implement the interface in your ListOfDetails activity
public class ListOfDetails extends FragmentActivity implements FragmentChangeListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
//Callback method indicating that an item with the given ID was selected.
public void onItemSelected(String id) {
// Performing logic to determine what fragment to start omitted
if (ifTwoPanes()) {
Fragment fragment = new DetailFragmentType1();
getSupportFragmentManager().beginTransaction().replace(R.id.aContainer, fragment).commit();
} else {
Intent newIntent = new Intent(this, SinglePaneFragmentWrapper.class);
newIntent.putExtra("id", id);
startActivity(newIntent);
}
}
// My attempt at making it possible to change displayed fragment from within fragments
public void changeDetailFragment(Fragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(null);
transaction.replace(R.id.aContainer, fragment);
transaction.commit();
}
// This is the interface implementation that will be called by your fragments
void onFragmentChangeRequested(Fragment newFragment) {
changeDetailFragment(newFragment);
}
}
Added listener to detail fragment
public class DetailFragmentType1 extends Fragment {
private FragmentChangeListener fragmentChangeListener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Actually you might not have an activity here.. you should probably be
// doing this in onAttach
//Activity a = getActivity();
//if (a instanceof ListOfDetails) {
// parent = (ListOfDetails) a;
//}
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button aButton = (Button) getActivity().findViewById(R.id.aButton);
aButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// parent.changeDetailFragment(new SubDetailFragment());
notifyFragmentChange(new SubDetailFragment());
}
});
}
#Override
public void onAttach(Activity activity) {
// This is called when the fragment is attached to an activity..
if (activity instanceof FragmentChangeListener) {
fragmentChangeListener = (FragmentChangeListener) activity;
} else {
// Find your bugs early by making them clear when you can...
if (BuildConfig.DEBUG) {
throw new IllegalArgumentException("Fragment hosts must implement FragmentChangeListener");
}
}
}
private void notifyFragmentChange(Fragment newFragment) {
FragmentChangeListener listener = fragmentChangeListener;
if (listener != null) {
listener.onFragmentChangeRequested(newFragment);
}
}
}
And implement the same interface to your single pane activity...
public class SinglePaneFragmentWrapper extends FragmentActivity implements FragmentChangeListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Duplicate logic must be performed to start fragment
// Performing logic to determine what fragment to start omitted
String id = getIntent().getStringExtra("id");
if(id == "DetailFragmentType1") {
Fragment fragment = new DetailFragmentType1();
getSupportFragmentManager().beginTransaction().replace(R.id.aContainer, fragment).commit();
} else {
...
}
}
// My attempt at making it possible to change displayed fragment from within fragments
public void changeDetailFragment(Fragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(null);
transaction.replace(R.id.aContainer, fragment);
transaction.commit();
}
// This is the interface implementation that will be called by your fragments
void onFragmentChangeRequested(Fragment newFragment) {
changeDetailFragment(newFragment);
}
}
Note the similarity between your single pane and your multi-pane activities.. this suggests that you could either put all of the duplicated code (changefragment etc) into a single activity that they both extend or that in maybe they are the same activities with different layouts...
I hope that helps, Good luck.
Regards,
CJ
I'm using the compatibility package to use Fragments with Android 2.2.
When using fragments, and adding transitions between them to the backstack, I'd like to achieve the same behavior of onResume of an activity, i.e., whenever a fragment is brought to "foreground" (visible to the user) after poping out of the backstack, I'd like some kind of callback to be activated within the fragment (to perform certain changes on a shared UI resource, for instance).
I saw that there is no built in callback within the fragment framework. is there s a good practice in order to achieve this?
For a lack of a better solution, I got this working for me:
Assume I have 1 activity (MyActivity) and few fragments that replaces each other (only one is visible at a time).
In MyActivity, add this listener:
getSupportFragmentManager().addOnBackStackChangedListener(getListener());
(As you can see I'm using the compatibility package).
getListener implementation:
private OnBackStackChangedListener getListener()
{
OnBackStackChangedListener result = new OnBackStackChangedListener()
{
public void onBackStackChanged()
{
FragmentManager manager = getSupportFragmentManager();
if (manager != null)
{
MyFragment currFrag = (MyFragment) manager.findFragmentById(R.id.fragmentItem);
currFrag.onFragmentResume();
}
}
};
return result;
}
MyFragment.onFragmentResume() will be called after a "Back" is pressed. few caveats though:
It assumes you added all
transactions to the backstack (using
FragmentTransaction.addToBackStack())
It will be activated upon each stack
change (you can store other stuff in
the back stack such as animation) so
you might get multiple calls for the
same instance of fragment.
I've changed the suggested solution a little bit. Works better for me like that:
private OnBackStackChangedListener getListener() {
OnBackStackChangedListener result = new OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager manager = getSupportFragmentManager();
if (manager != null) {
int backStackEntryCount = manager.getBackStackEntryCount();
if (backStackEntryCount == 0) {
finish();
}
Fragment fragment = manager.getFragments()
.get(backStackEntryCount - 1);
fragment.onResume();
}
}
};
return result;
}
After a popStackBack() you can use the following callback : onHiddenChanged(boolean hidden) within your fragment
The following section at Android Developers describes a communication mechanism Creating event callbacks to the activity. To quote a line from it:
A good way to do that is to define a callback interface inside the fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary.
Edit:
The fragment has an onStart(...) which is invoked when the fragment is visible to the user. Similarly an onResume(...) when visible and actively running. These are tied to their activity counterparts.
In short: use onResume()
If a fragment is put on backstack, Android simply destroys its view. The fragment instance itself is not killed. A simple way to start should to to listen to the onViewCreated event, an put you "onResume()" logic there.
boolean fragmentAlreadyLoaded = false;
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState == null && !fragmentAlreadyLoaded) {
fragmentAlreadyLoaded = true;
// Code placed here will be executed once
}
//Code placed here will be executed even when the fragment comes from backstack
}
In my activity onCreate()
getSupportFragmentManager().addOnBackStackChangedListener(getListener());
Use this method to catch specific Fragment and call onResume()
private FragmentManager.OnBackStackChangedListener getListener()
{
FragmentManager.OnBackStackChangedListener result = new FragmentManager.OnBackStackChangedListener()
{
public void onBackStackChanged()
{
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (currentFragment instanceof YOURFRAGMENT) {
currentFragment.onResume();
}
}
};
return result;
}
A little improved and wrapped into a manager solution.
Things to keep in mind. FragmentManager is not a singleton, it manages only Fragments within Activity, so in every activity it will be new. Also, this solution so far doesn't take ViewPager into account that calls setUserVisibleHint() method helping to control visiblity of Fragments.
Feel free to use following classes when dealing with this issue (uses Dagger2 injection). Call in Activity:
//inject FragmentBackstackStateManager instance to myFragmentBackstackStateManager
FragmentManager fragmentManager = getSupportFragmentManager();
myFragmentBackstackStateManager.apply(fragmentManager);
FragmentBackstackStateManager.java:
#Singleton
public class FragmentBackstackStateManager {
private FragmentManager fragmentManager;
#Inject
public FragmentBackstackStateManager() {
}
private BackstackCallback backstackCallbackImpl = new BackstackCallback() {
#Override
public void onFragmentPushed(Fragment parentFragment) {
parentFragment.onPause();
}
#Override
public void onFragmentPopped(Fragment parentFragment) {
parentFragment.onResume();
}
};
public FragmentBackstackChangeListenerImpl getListener() {
return new FragmentBackstackChangeListenerImpl(fragmentManager, backstackCallbackImpl);
}
public void apply(FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
fragmentManager.addOnBackStackChangedListener(getListener());
}
}
FragmentBackstackChangeListenerImpl.java:
public class FragmentBackstackChangeListenerImpl implements FragmentManager.OnBackStackChangedListener {
private int lastBackStackEntryCount = 0;
private final FragmentManager fragmentManager;
private final BackstackCallback backstackChangeListener;
public FragmentBackstackChangeListenerImpl(FragmentManager fragmentManager, BackstackCallback backstackChangeListener) {
this.fragmentManager = fragmentManager;
this.backstackChangeListener = backstackChangeListener;
lastBackStackEntryCount = fragmentManager.getBackStackEntryCount();
}
private boolean wasPushed(int backStackEntryCount) {
return lastBackStackEntryCount < backStackEntryCount;
}
private boolean wasPopped(int backStackEntryCount) {
return lastBackStackEntryCount > backStackEntryCount;
}
private boolean haveFragments() {
List<Fragment> fragmentList = fragmentManager.getFragments();
return fragmentList != null && !fragmentList.isEmpty();
}
/**
* If we push a fragment to backstack then parent would be the one before => size - 2
* If we pop a fragment from backstack logically it should be the last fragment in the list, but in Android popping a fragment just makes list entry null keeping list size intact, thus it's also size - 2
*
* #return fragment that is parent to the one that is pushed to or popped from back stack
*/
private Fragment getParentFragment() {
List<Fragment> fragmentList = fragmentManager.getFragments();
return fragmentList.get(Math.max(0, fragmentList.size() - 2));
}
#Override
public void onBackStackChanged() {
int currentBackStackEntryCount = fragmentManager.getBackStackEntryCount();
if (haveFragments()) {
Fragment parentFragment = getParentFragment();
//will be null if was just popped and was last in the stack
if (parentFragment != null) {
if (wasPushed(currentBackStackEntryCount)) {
backstackChangeListener.onFragmentPushed(parentFragment);
} else if (wasPopped(currentBackStackEntryCount)) {
backstackChangeListener.onFragmentPopped(parentFragment);
}
}
}
lastBackStackEntryCount = currentBackStackEntryCount;
}
}
BackstackCallback.java:
public interface BackstackCallback {
void onFragmentPushed(Fragment parentFragment);
void onFragmentPopped(Fragment parentFragment);
}
This is the correct answer you can call onResume() providing the fragment is attached to the activity. Alternatively you can use onAttach and onDetach
onResume() for the fragment works fine...
public class listBook extends Fragment {
private String listbook_last_subtitle;
...
#Override
public void onCreate(Bundle savedInstanceState) {
String thisFragSubtitle = (String) getActivity().getActionBar().getSubtitle();
listbook_last_subtitle = thisFragSubtitle;
}
...
#Override
public void onResume(){
super.onResume();
getActivity().getActionBar().setSubtitle(listbook_last_subtitle);
}
...
public abstract class RootFragment extends Fragment implements OnBackPressListener {
#Override
public boolean onBackPressed() {
return new BackPressImpl(this).onBackPressed();
}
public abstract void OnRefreshUI();
}
public class BackPressImpl implements OnBackPressListener {
private Fragment parentFragment;
public BackPressImpl(Fragment parentFragment) {
this.parentFragment = parentFragment;
}
#Override
public boolean onBackPressed() {
((RootFragment) parentFragment).OnRefreshUI();
}
}
and final extent your Frament from RootFragment to see effect
My workaround is to get the current title of the actionbar in the Fragment before setting it to the new title. This way, once the Fragment is popped, I can change back to that title.
#Override
public void onResume() {
super.onResume();
// Get/Backup current title
mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
.getTitle();
// Set new title
((ActionBarActivity) getActivity()).getSupportActionBar()
.setTitle(R.string.this_fragment_title);
}
#Override
public void onDestroy() {
// Set title back
((ActionBarActivity) getActivity()).getSupportActionBar()
.setTitle(mTitle);
super.onDestroy();
}
I have used enum FragmentTags to define all my fragment classes.
TAG_FOR_FRAGMENT_A(A.class),
TAG_FOR_FRAGMENT_B(B.class),
TAG_FOR_FRAGMENT_C(C.class)
pass FragmentTags.TAG_FOR_FRAGMENT_A.name() as fragment tag.
and now on
#Override
public void onBackPressed(){
FragmentManager fragmentManager = getFragmentManager();
Fragment current
= fragmentManager.findFragmentById(R.id.fragment_container);
FragmentTags fragmentTag = FragmentTags.valueOf(current.getTag());
switch(fragmentTag){
case TAG_FOR_FRAGMENT_A:
finish();
break;
case TAG_FOR_FRAGMENT_B:
fragmentManager.popBackStack();
break;
case default:
break;
}