Imagine one activity with 3 fragments: starts showing the first one, select a menu option and go to the second one, select another option and go to the 3rd fragment and select again the first option an return to the second one.
f1 -> f2 -> f3 -> f2
When I press back I want the app returns to fragment 3 and when I press back again it should return to fragment 1 and if press back again, close the app.
Something like if the fragment exists, move it to top of the stack and if not, create it.
Thank you!
Here is solution I came up over time.
The idea is following, you need to keep a stack data structure and whenever you add a fragment add it to stack as well, then override onBackPress method and check if stack is not empty then replace your fragment container with new fragment from top of the stack when it is empty do super.onbackpress
So here is a parent class for all kind of fragment based navigation.
public abstract class FragmentsStackActivity extends BaseActivity {
public static final String TAG_BUNDLE = "bundle_tag";
protected final Bundle fragmentArgs = new Bundle();
protected Stack<Fragment> fragments = new Stack<>();
abstract protected void setupFragments();
public void setFragmentArguments(Fragment fragment, Bundle arguments){
if(!fragments.isEmpty() && fragments.peek()!=fragment){
fragment.setArguments(arguments);
}
}
public void setFragmentFromStack() {
if(!fragments.isEmpty()) {
Fragment fragment = fragments.peek();
final Fragment oldFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (oldFragment == null || oldFragment != fragment) {
getFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
final FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
//transaction.setCustomAnimations(R.anim.animator_left_right_in, R.anim.animator_left_right_in);
transaction.replace(R.id.fragment_container, fragment).commit();
}
}else {
finish();
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
//TODO need to save fragment stack
}
}
example of an activity that extends this class
public class LoginActivity extends FragmentsStackActivity{
private final MyFragment1 fragment1 = new MyFragment1();
private final MyFragment2 fragment2 = new MyFragment2();
private final User mUser = new User();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
setupFragments();
setFragmentFromStack();
}
#Override
protected void setupFragments() {
fragments.add(fragment2);
//fragment2.setNotifier(this); // I use notifiers listener but you can choose whatever convenient for you
Bundle fragmentArgs = new Bundle();
fragmentArgs.putBoolean(Constants.TAG_LOGIN, true);
fragmentArgs.putParcelable(User.TAG, mUser);
fragmentArgs.putInt(Constants.TYPE, getIntent().getIntExtra(Constants.TYPE, 0));
fragment2.setArguments(fragmentArgs);
//fragment1.setNotifier(this); // I use notifiers listener but you can choose whatever convenient for you
}
// this method teals with handling messages from fragments in order to provide navigation
// when some actions taken inside the fragment, you can implement your own version
public void onReceiveMessage(String tag, Bundle bundle) {
switch (tag) {
case MyFragment2.TAG_BACK:
case MyFragment1.TAG_BACK:
fragments.pop();
setFragmentFromStack();
break;
case MyFragment2.TAG_NEXT:
fragment1.setArguments(bundle);
fragments.add(fragment1);
setFragmentFromStack();
break;
case MyFragment1.TAG_NEXT:
goToWelcomeScreen(bundle);
finish();
break;
}
}
private void goToWelcomeScreen(Bundle bundle){
}
}
You can implement this with the help of the following code:
// transaction.replace(R.id.detailFragment, frag1);
Transaction.remove(null).add(frag1) // frag1 on view
// transaction.replace(R.id.detailFragment, frag2).addToBackStack(null);
Transaction.remove(frag1).add(frag2).addToBackStack(null) // frag2 on view
// transaction.replace(R.id.detailFragment, frag3);
Transaction.remove(frag2).add(frag3) // frag3 on view
And for better understanding, have a ook at the following snippet:
// Works with either the framework FragmentManager or the
// support package FragmentManager (getSupportFragmentManager).
getSupportFragmentManager().beginTransaction()
.add(detailFragment, "detail")
// Add this transaction to the back stack
.addToBackStack()
.commit();
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// Update your UI here.
}
});
have a look here http://developer.android.com/training/implementing-navigation/temporal.html
Related
I've already searched about this and found nothing, which helps me to solve my problem. In my Code I have a SpaceNavigationView (like BottomNavigationView) with five Fragments.
So in Fragment A, I've put a Recyclerview. If an item of the Recyclerview gets clicked, it will replace the current fragment with a new child fragment B.
In Fragment B I've set a Chronometer, which should count the time, when it gets pressed.
Now if I switch from Fragment B to Fragment C and go back to Fragment B, the Chronometer starts by zero, because the fragment was replaced.
I've tried to used onSaveInstanceState, so that it can be called when Fragment is recreated, but this doesn't work for me.
Here's a piece of the HomeActivity, which includes all the Fragments.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
init();
setFragment(fragmentHome);
navigationView.initWithSaveInstanceState(savedInstanceState);
navigationView.addSpaceItem(new SpaceItem("", R.drawable.bottom_baby));
navigationView.addSpaceItem(new SpaceItem("", R.drawable.bottom_advise));
navigationView.addSpaceItem(new SpaceItem("", R.drawable.ic_favorite_black_24dp));
navigationView.addSpaceItem(new SpaceItem("", R.drawable.ic_settings));
navigationView.setSpaceOnClickListener(new SpaceOnClickListener() {
#Override
public void onCentreButtonClick() {
setFragment(fragmentPlayground);
navigationView.setCentreButtonSelectable(true);
}
#Override
public void onItemClick(int itemIndex, String itemName) {
switch (itemIndex) {
case 0:
setFragment(fragmentHome);
return;
case 1:
setFragment(fragmentAdvising);
return;
case 2:
setFragment(fragmentMemories);
return;
case 3:
setFragment(fragmentSettings);
return;
default:
setFragment(fragmentHome);
return;
}
}
#Override
public void onItemReselected(int itemIndex, String itemName) {
Toast.makeText(HomeActivity.this, itemIndex + " " + itemName, Toast.LENGTH_SHORT).show();
}
});
}
private void setFragment(Fragment fragment) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.container, fragment);
fragmentTransaction.commit();
}
So if i navigate now to FragmentHome and use the OnClickListener for Reycleritems, I will switch to Fragment_Chronograph
#Override
public void onItemClick(int position) {
switch (position) {
case 0:
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment_chronograph).commit();
}
So now I'm in Fragment_Chronograph and want to save the base for Chronograph. I will save the variable in onSavedInstanceState, which gets called when Activity is Paused.
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
startTime = SystemClock.elapsedRealtime() - chronometerLeft.getBase();
outState.putLong(CHRONOLEFT_TIME_SAVE_ID,startTime);
super.onSaveInstanceState(outState);
#Override
public void onPause() {
super.onPause();
onSaveInstanceState(new Bundle());
}
At the end i've put this code for restore in the OnCreate Method:
if (savedInstanceState != null) {
startTime = savedInstanceState.getLong(CHRONOLEFT_TIME_SAVE_ID,0);
chronometerLeft.setBase(startTime - SystemClock.elapsedRealtime());
chronometerLeft.start();
The OnSaveInstanceState gets called, but in the OnCreate Method it won't be called. I would be very thankful if someone could help me with this problem. I'm searching for days and didnt get a solution.
in set fragment method , first find fragment with id and then check if it's not null replace that
Fragment fragment = getSupportFragmentManager().findFragmentByTag(TAG_PLACEHOLDER);
if (fragment == null) {
fragment = new PlaceholderFragment();
}
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.container, fragment, TAG_PLACEHOLDER)
.commit();
Note : you must add tag to your fragments
#Arvin
FragmentHome ist initiated in the init() method;
private void init() {
navigationView = findViewById(R.id.space);
fragmentHome = new FragmentHome();
fragmentAdvising = new FragmentAdvising();
fragmentMemories = new FragmentMemories();
fragmentSettings = new FragmentSettings();
fragmentPlayground = new FragmentPlayground();
============= UPDATE =================
Problem was solved. I didn't have to use OnSavedInstanceState. I used a Helperclass to store the variable of the Chronometerbase. In OnCreate method i check if the variable is not null, then restore the before saved base.
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 implemented AppCompatActivity that i used for fragment transaction. Only one AppCompatActivity like:
public class UIActivity extends AppCompatActivity {
...
}
The fragment can begin another transaction, leading to having more than one fragment of the same class in the back stack. (illustration)
public class UiFragment extends Fragment implements FileChooserListener {
mybutton.setOnclickListener(){
// do some stuff, met some condition and add fragment ( of UiFragment), possibly inflating another view, add previous fragment to back stack
}
}
This works fine except that even when i implemented onSaveInstance,on restore instance inflate the firstfragment while other are lost. I want to resume from where the user stopped and being able to access fragment in back stack when the user press the back button.
IMPLEMENTATION
in UIActivity
UIFragment uif;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
overridePendingTransition(R.anim.push_up_in, R.anim.push_up_out);
setContentView(R.layout.activity_ui);
if (savedInstanceState == null) {
uif = new UIFragment();
Bundle bundle = new Bundle();
bundle.putSerializable("step", getIntent().getExtras().getSerializable("step"));
bundle.putString(UIFragment.STEP_KEY, getIntent().getExtras().getString(UIFragment.STEP_KEY));
uif.setArguments(bundle);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.content_frame, uif, "myfragment").commitAllowingStateLoss();
}
}
public void onSaveInstanceState(Bundle outState){
getSupportFragmentManager().putFragment(outState,"myfragment",uif);
}
public void onRestoreInstanceState(Bundle inState){
uif = (UIFragment)getSupportFragmentManager().findFragmentByTag("myfragment");
}
snippet that adds UIFragment to the stack
public void addNewUIFragment(){
UIFragment uif= new UIFragment ();
Bundle bundle = new Bundle();
bundle.putSerializable("step", step);
bundle.putString(UIFragment.STEP_KEY, sData);
uif.setArguments(bundle);
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.addToBackStack(null);
transaction.setCustomAnimations(R.anim.slide_in, R.anim.slide_out, R.anim.back_slide_in,
R.anim.back_slide_out);
transaction.add(R.id.content_frame, uif, "myfragment").commitAllowingStateLoss();
}
I have two fragments that are in the main activity and i want to refresh them when something occurs.
Now the code works for second fragment, but won't work for the first, and i am not sure why.
I have been looking at the code for about an hour, and i can't seem to find a reason.
Here is the code
public class MainActivity extends FragmentActivity {
Fragment frag,frag2;
FragmentManager fm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String screen = getString(R.string.screen_type);
/*
* Get a reference to fragment manager
* Wire the container to represent fragment
*/
fm = getSupportFragmentManager();
frag = fm.findFragmentById(R.id.container);
if(screen.equals("large")){
frag2 = fm.findFragmentById(R.id.containerDetails);
loadFragments(frag,frag2,fm);
}
/*Loads the fragment into the activity*/
else
loadFragment(frag,fm);
}
private void loadFragments(Fragment frag, Fragment frag2, FragmentManager fm) {
if(frag == null && frag2 == null){
frag = new DisplayFragment();
frag2 = new DetailsFragment();
fm.beginTransaction().add(R.id.container,frag).add(R.id.containerDetails, frag2).commit();
}
}
private void loadFragment(Fragment frag, FragmentManager fm) {
if(frag == null){
frag = new DisplayFragment();
fm.beginTransaction().add(R.id.container,frag).commit();
}
}
public void updateDetails(int position) {
// Reload current fragment
if(frag2!=null)fm.beginTransaction().remove(frag2).commit();
frag2 = new DetailsFragment();
Bundle b = new Bundle();
b.putInt("Id",position);
frag2.setArguments(b);
fm.beginTransaction().add(R.id.containerDetails, frag2).commit();
}
public void updateDisplay() {
// Reload current fragment
if(frag!=null)fm.beginTransaction().remove(frag).commit(); //THIS IS ALWAYS NULL FOR SOME REASON
frag = new DisplayFragment();
fm.beginTransaction().add(R.id.container, frag).commit();
}
public void refreshDetails() {
// Reload current fragment
if(frag2!=null)fm.beginTransaction().remove(frag2).commit();
frag2 = new DetailsFragment();
fm.beginTransaction().add(R.id.containerDetails, frag2).commit();
}
}
The first fragment is always null, and it doesn't get removed, instead another fragment is pasted over that, and creates a mess.
Try using replace() method rather than add()
I am useing This Sort of Code To Handle Three Fragment in Main Activity ...
FragmentA is Fixed it One Frame .. I change FragmentB and FragmentC on Button Click on FragmentA.
his Code is Running well either in Portrait or Landscape view .Here is The Code bellow.
public class MainActivity extends FragmentActivity implements
OnSwitchClickListener {
FragmentManager manager;
FragmentA fragA;
FragmentB fragB;
FragmentC fragC;
boolean fragBSet = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragA = new FragmentA();
fragB = new FragmentB();
fragC = new FragmentC();
manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.add(R.id.a_container, fragA, "frag A");
if (fragBSet) {
ft.add(R.id.bc_container, fragC, "frag C");
} else {
ft.add(R.id.bc_container, fragB, "frag B");
}
fragBSet = true;
ft.commit();
}
#Override
public void onSwitchClick(View v) {
Toast.makeText(getApplicationContext(), "Switch clkick from Activity",
Toast.LENGTH_LONG).show();
FragmentTransaction ft = manager.beginTransaction();
if (fragBSet) {
ft.remove(fragB);
ft.add(R.id.bc_container, fragC, "frag C");
fragBSet = false;
} else {
ft.remove(fragC);
ft.add(R.id.bc_container, fragB, "frag B");
fragBSet = true;
}
ft.commit();
}
This Code is Running well either in Portrait or Landscape view ... But When Ever I change the Orientation The Two Fragments Override One Another.
Need Solution.
When your orientation change, your Activity is recreated.
"In the scenario where a user rotates their device, Android will destroy your
application’s activity(s). Before destroying them it calls, onSaveInstanceState,
allowing developers to persist data. Once the activity is recreated post
rotation, the OS will call onRestoreInstanceState giving developers a chance to
restore the application state pre-rotation."
You should try to save your Fragments State.
putFragment: Put a reference to a fragment in a Bundle. This Bundle can be persisted as saved state, and when later restoring getFragment(Bundle, String) will return the current instance of the same fragment.
#Override
protected void onSaveInstanceState(Bundle outState) {
FragmentManager manager = getFragmentManager();
manager.putFragment(outState, MyFragment.TAG, mMyFragment);
}
getFragment: Retrieve the current Fragment instance for a reference previously placed with putFragment(Bundle, String, Fragment).
private void instantiateFragments(Bundle inState) {
FragmentManager manager = getFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
if (inState != null) {
mMyFragment = (MyFragment) manager.getFragment(inState, MyFragment.TAG);
} else {
mMyFragment = new MyFragment();
transaction.add(R.id.fragment, mMyFragment, MyFragment.TAG);
transaction.commit();
}
}
restoreFragment:
#Override
protected void onRestoreInstanceState(Bundle inState) {
instantiateFragments(inState);
}
Hope this helps.