Android Fragments: Prevent mutiple addToBackStack calls - java

public void onClick(View v) {
switch(v.getId())
{
case R.id.bAddYourNumber:
FragmentTransaction trans = getFragmentManager().beginTransaction();
trans.replace(R.id.root_frame, new InsertPastNumbersFragment());
trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
trans.addToBackStack(TAG);
trans.commit();
break;
}
When the button is pressed, the fragment is replaced and the previous one placed into back stack.
The problem is if the button is pressed several times, the same fragment is placed into backstack multiple times which results in the phone back button having to be pressed x amount of times the button was pressed to go back to the previous fragment.
Is there a way to control this to only add to back stack once?

Give your transaction a tag:
trans.replace(R.id.root_frame, new InsertPastNumbersFragment())
becomes
trans.replace(R.id.root_frame, new InsertPastNumbersFragment(), PAST_NUM_TAG)
Then, before handling the transaction, check:
if (getFragmentManager().findFragmentByTag(PAST_NUM_TAG) == null) {
// Fragment hasn't yet been added, do the transaction
} else {
// Fragment has already been added
}
Alternatively, if you have a button that triggers this, it might make more sense to just disable the button after you add the fragment to prevent the user from being able to press it more than once.

Related

OnBackPress behaving differently in android

I have 3 activities: A,B,C. When I click on an item in RecyclerView in activity A activity B will be opened. There are a few String values which I have passed from A to B.
In activity B when I click on a button I go to activity C. In C when I press back button from my phone(android default back button) it comes back normally to activity B. But I have also put a back button in ToolBar using parent activity. When I press that button it shows me error that the String value trying to retrieve in activity B is null. I understand why the error is showing. It's obviously because there is no string passed from C to B to fetch.
But why am I not getting this error when I press back from my phones default button? How can I solve this?
Activity A
Intent intent = new Intent(getActivity(), Chat.class);
intent.putExtra("Recievers_Id", userId);
intent.putExtra("Recievers_Name", userName);
startActivity(intent);
Activity B
MessageSenderId = mAuth.getCurrentUser().getUid();
MessageRecieverId = getIntent().getStringExtra("Recievers_Id");
MessageRecieverName = getIntent().getStringExtra("Recievers_Name");
Activity C
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
Getting an error on
MessageRecieverId = getIntent().getStringExtra("Recievers_Id");
MessageRecieverName = getIntent().getStringExtra("Recievers_Name");
because obviously nothing to fetch... how do i solve this
Add onClickListener to the back button of your toolbar:
toolbar.setNavigationOnCLickListener(new View.OnClickListener){
#Override
public void onClick(View view) {
finish();
}
});
If you are getting error for null then try this:
if(getIntent().getStringExtra("Recievers_Id") != null){
MessageRecieverId = getIntent().getStringExtra("Recievers_Id");
}
if(getIntent().getStringExtra("Recievers_Name") != null){
MessageRecieverName = getIntent().getStringExtra("Recievers_Name");
}
On activity B you should check is Intent has rquired Extra.
Intent i = getIntent();
if (i.hasExtra("Recievers_Id")) {
messageRecieverId = i.getStringExtra("Recievers_Id");
}
if (i.hasExtra("Recievers_Name")) {
messageRecieverName= i.getStringExtra("Recievers_Name");
}
You can set the working of back button on toolbar as:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
// Respond to the action bar's Up/Home button
case android.R.id.home:
//NavUtils.navigateUpFromSameTask(this);
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
First of all, you need to understand what happens when user presses navigation up button and what happens when onBackPressed.
Based on the documentation:
If the parent activity has launch mode <singleTop>, or the up intent contains FLAG_ACTIVITY_CLEAR_TOP, the parent activity is brought
to the top of the stack, and receives the intent through its
onNewIntent() method.
If the parent activity has launch mode , and the up intent does not contain FLAG_ACTIVITY_CLEAR_TOP, the parent activity
is popped off the stack, and a new instance of that activity is
created on top of the stack to receive the intent.
Where as when back button is pressed, it navigate, in reverse chronological order, through the history of screens. In this case Activity won't be re-created.
To summarize, the lauch mode of your ActivityB in AndroidManifest.xml must be standard. In this case the activity will be recreated when you press the navigation up button. But when back button is pressed, ActivityB is just brought to front, hence the difference in behavior.
Solution:
Approach 1: handle Up button by yourself
#Override
public boolean onOptionsItemSelected(MenuItem item) {
if(item.getItemId()== android.R.id.home {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
Approach 2:
Set launch mode of Activity B to singleTop in AndroidManifest.xml. Remember to check the implication of changing the launch mode.
The "standard" and "singleTop" modes differ from each other in just
one respect: Every time there's a new intent for a "standard"
activity, a new instance of the class is created to respond to that
intent. Each instance handles a single intent. Similarly, a new
instance of a "singleTop" activity may also be created to handle a new
intent. However, if the target task already has an existing instance
of the activity at the top of its stack, that instance will receive
the new intent (in an onNewIntent() call); a new instance is not
created.

Prevent ActionBar back button to recreate the MainActivity

How can I prevent the ActionBar back button (we gonna say ABBB) of my SecondActivity to recreate the MainActivitywhen clicked ?
I have a ListView in the MainActivity and it can be edited using the SecondActivity. The problem is that when the user presses the ABBB, the ListView is reset to default...
However, when the user clicks my OK button or presses physical back button, there is not any problem because I'm using finish();
#Override
public void onBackPressed() {
finish();
}
If I use this code... :
switch (item.getItemId()) {
case (android.R.id.home):
finish();
}
...there is the same problem because, I guess, this method is called after "Android's code".
How can I totally override Android ABBB's clicked code to set it to just call finish(); ?
The ideal scenario is, when are you in the SecondActivity (I take it that, this means that you are in Edit mode), and you press the device back button or ABBB, you show a subtle alert to the user saying "do they really want to dismiss the editing", or go ahead and apply the edit done as in the Contacts application.
So that being said, if all you require is to finish() the activity on press of ABBB, the code that you shown above should work fine (though you need to put return true; after finish()). It works for me. Try the below one in your activity.
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed(); // or finish();
return true;
}
return super.onOptionsItemSelected(item);
}
I put onBackPressed(); because your ABBB with now imitate your device back button.
Or, you can set your parent activity's launch mode as -
android:launchMode="singleTop"
that will do all without changing anything.
Reference

Trying to call a method from 1st class activity and keep the same argument in a 2nd class activity (android studio)

Currently finishing up an app I have been working on but I'm kinda stuck on this last thing.
The app has 2 activities, one with buttons that are categories and the other shows the information according to the button pressed. For example if you click the button Fast food, it goes to the 2nd screen and displays info on that.
I'm trying to get a refresh button on the 2nd activity that will call a method in the 1st activity to refresh new information depending on the button pressed. The problem is that I don't know how to make it so the method keeps the same argument when called. What I mean is, if fast food was clicked, the refresh button would get new info that still relates to the fast food category.
Here's the relevant code in the Main activity:
public void yelpSearch(View view) {
switch (view.getId()) {
case R.id.button: buttoncategory = "Restaurant";
break;
case R.id.button2: buttoncategory = "Chinese restaurant";
break;
case R.id.button3: buttoncategory = "Fast Food";
break;
and this on the 2nd Activity
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.refresh:
Refresh();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void Refresh() {
MainActivity mActivity = new MainActivity();
mActivity.yelpSearch();
}
I'm not sure what to put inside mActivity.yelpSearch();
I tried using (View view) but it'll say cannot resolve symbol view. And if I make a local variable for view, it'll say not initialized and I don't know what to set it as
Any help would be awesome, been googling on this for a while now and searched through tons of questions on here as well. I'm still new at this so bear with me
I think you want to pass data between activities?
First Activity(Send DATA):
String data="YourXXXdata"
Intent intent = new Intent(getBaseContext(), MainActivity.class);
intent.putExtra("DATANAME", data);
startActivity(intent)
second Activity(Receive DATA):
Bundle extras = getIntent().getExtras();
if (extras != null) {
String value = extras.getString("DATANAME");
}
The data now is in String value
*you have to put in data the value you need depend you preview select button.
Make your yelpSearch method static
and call it like this from 2nd activity MainActivity.yelpSearch();

Which method is being called after popBackStack?

I have an activity where I am calling three fragments - each depending on each other:
A(ctivity) -> f1 (Fragment one, title {is|should}: list) -> f2 (Fragment two, title {is|should}: overview) -> f3 (Fragment three, title {is|should}: detail)
ATM I use the following method call to jump backwards:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
FragmentManager fragmentManager = getSupportFragmentManager();
if (fragmentManager.getBackStackEntryCount()>0){
fragmentManager.popBackStack();
}
}
}
This works fine.
I am overriding the ActionBar title in each fragment like this:
ActionBar bar = getSherlockActivity().getSupportActionBar();
bar.setTitle(R.string.title_f3);
When navigating forward (like shown above) this works flawlessly but navigating backwards the title of the ActionBar isn´t updated:
f3 (title {is|should}: detail) -> f2 (title {is}: detail, {should}: overview) -> f1 (title {is}: detail, {should}: list)
Obviously I could just update it again when the fragment is shown. But my debugger never stops in any of the methods I´d except which would be called like onResume().
So is there actually any method being called in a previous fragment after popBackStack() ?
I know this is a bit late for an answer but for anyone who navigates here this might help!
First thing is first: popBackStack()doesn't pop a fragment, it pops a fragment transaction. So the last fragment transaction is reversed upon being called. If you were displaying FragmentA currently and your transaction was:
fragmentTransaction.replace(R.id.your_layout, fragmentB);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
It would replace FragmentA with FragmentB, and add that transaction (not the fragment) to the back stack. If you then hit the back button, it pops the transaction off the back stack, which was "replace this FragmentA with a FragmentB". Essentially, this instruction reverses the last transaction and removes it from the stack of transactions carried out. If the original FragmentA still exists, it uses that one. If it's been destroyed, it makes a new one.
So, if the Fragment hasn't been destroyed, then recalling the fragment after using on popBackStack(), the onStart() and onResume() methods are called. If the Fragment has been destroyed previously, then the lifecycle methods will be called starting from onAttach(). It's the same as pressing the back button on Activities.
Now the important bit, what happens re fragment lifecycle when we pop off back stack? Well as said before the fragment transaction is reversed so:
Scenario 1: Your fragmentB didn't already exist before transaction.
In this case the onCreate() and onAttach() methods are called during the transaction so the fragment will be destroyed and detached if you call popBackStack() and reverse the transaction (Note FragmentA probably already existed so replacing it wont destroy it as we're not undoing a fragment creation). In this case the lifecycle methods will be called starting from onAttach().
Scenario 2: Your fragmentB did already exist before transaction. In this case the fragment won't be destroyed and the next time you access it the onStart() and onResume() methods are called.
This fellow here explains a few things about using popbackstack() http://vinsol.com/blog/2014/09/19/transaction-backstack-and-its-management/ and the fragment lifecycle http://vinsol.com/blog/2014/10/22/fragment-view-state-retention-a-dirty-solution/. The other related posts are worth reading too!
use addOnBackStackChangedListener method in your BaseActivity, which will be called any time backstack changes
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager fm = getSupportFragmentManager();
if (fm != null) {
int backStackCount = fm.getBackStackEntryCount();
if (backStackCount == 0) {
if (getSupportActionBar() != null) {
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_menu);
}
setToolbarTittle(R.string.app_name);
} else {
if (getSupportActionBar() != null) {
getSupportActionBar().setHomeAsUpIndicator(R.drawable.back);
}
}
}
}
});
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();
}
There's also one other good source that you can read through.
I think, the important point is the difference of the transaction you performed on Fragment B. If it's add, then no lifecycle methods are called on Fragment A, if it's replace, you will get some calls on those methods.
I use following workaround:
1) set 1st fragment setHasOptionsMenu(false) before add 2nd fragment on top of 1st one.
2) set 1st fragment setHasOptionsMenu(true) in onOptionsItemSelected() after return from 2nd fragment.
3) onCreateOptionsMenu() of 1st fragment should be called and you can change actionbar here.
But I want to know a better solution.
You can find a nice Fragment lifecycle diagram in the android docs here. So yes, if the fragment is brought to the foreground from the backstack onCreateView(), onActivityCreated(), onStart() and onResume() are called.

Android Trying to display a welcome fragment when app starts, but not after

I've been looking for a way to have the blank detail side of my fragment layout host a welcome screen (or something - login perhaps) on start up. Afterwards, when a user presses one of the left side menu items, I'd like to eliminate the fragment for the remainder of the program run. I don't want to add it to the backstack, as that messes up my configuration changes. I've considered using shared prefs to host a boolean about whether the fragment has been displayed. The only concern with this method is where to safely reset the boolean value for the next run of the app. I'm of the impression that there's no gaurantee that the onStop, onDetach etc. will definitely get called upon closing of the app, so if the app got closed in the wrong state, it would be rendered useless ( the first fragment wouldn't display - crash )
Anyone have any ideas on how I could implement a filler for the right side of the app upon startup?
I've been trying to add something to the onCreate of my main activity thus far with no success.
Thanks in advance.
Ken
If your fragment can be part of its own Activity, you can use the android:noHistory="true" attribute to keep the Activity off of the backstack. If your user tries to navigate backwards, it'll hit the bottom of the backstack twice before exiting your application.
If you can't split your fragment into its own activity, noHistory may not work -- I can't say as I haven't tried it myself.
I was able to come up with a solution to creating a welcome or login screen which will display both fragments and activities from the main activity. Seems to be working fine as tested.
private boolean welcomeShown;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_list);
if (findViewById(R.id.item_detail_container) != null) {
mTwoPane = true;
((MainFragment) getSupportFragmentManager().findFragmentById(
R.id.item_list)).setActivateOnItemClick(true);
}
if (savedInstanceState != null){
welcomeShown = savedInstanceState.getBoolean("displayed");
}
if(!welcomeShown){
if (mTwoPane){
WelcomeFragment welcomeFragment = new WelcomeFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.item_detail_container, welcomeFragment)
.commit();
}
else{
Intent welcomeIntent = new Intent(this, WelcomeActivity.class);
startActivity(welcomeIntent);
welcomeShown = true;
}
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("displayed", true);
}

Categories