I am working on a app where I have only one Login activity and one main activity. I replace fragments down to three level ie like A->B->C or A->B->D and more scenarios like this.I keep them on back stack and on back press retrieve them.Like on backpress of C , B appears. I am having a scenario where I receive push notification then on click of it the app should redirect to C. Now to achieve it I have to go through the launch screen. It means that before clicking on notification if I am on D and I get notification for C then I will land on C , but on backpress I will not come to D but to B because A->B>C . So is there a structure for one activity and all fragments where even if I click on notification,I can get back the flow as it is before the notification arrived ..??? ie directly maintain C to D and then regular flow as it is.
Thank you.
It may depend slightly on what the contents of those fragments are but I think actually the Android docs would suggest the behaviour you have at the moment is actually the correct one. Essentially once you've navigated into the app from a notification you the navigation should be the same as if you'd entered that point in the app normally by navigating through the screens. It should have the previous navigation history cleared.
The docs elaborate on this:
https://developer.android.com/design/patterns/navigation.html#into-your-app
Firstly implements TabLayout.OnTabSelectedListener to your fragment hosted activity,override methods then on onTabSelected get tab position(Let's say tabPosition),from this position try following code in same activity(with required changes):
#Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
// TODO Auto-generated method stub
if ((keyCode == KeyEvent.KEYCODE_BACK))
{
if(tabPosition!=0)
{
pager.setCurrentItem(tabPosition- 1);
return true;
}
else
{
onBackPressed();
}
}
return super.onKeyDown(keyCode, event);
}
You can implement a custom backstack of you own.
So if you have a history that looks like this:
A->B->C->D
If an event then causes the history to add C once more, e.g. the notification you specify above, then what you specify above suggests you want the history to look like this afterward:
A->B->C->D->C
And what you get currently, which you don't want, is this:
A->B->C
To do this you can implement custom fragment history handling in your Activity. Where you would normally add your fragment transition to the backstack, instead add the fragment to an ArrayList or similar entity. I have thrown together some code below:
private ArrayList<Fragment> mFragmentHistory = new ArrayList<>();
private void loadFragment(Fragment fragment, boolean addToHistory) {
// your fragment transition code here without adding to backstack
// add the fragment to your custom history
if (addToHistory) {
mFragmentHistory.add(fragment);
}
}
You will then need to implement handling code in the onBackPressed method of the same Activity:
#Override
public void onBackPressed() {
// if the history is not at the "top", i.e. only one (or 0) fragments in history
if (mFragmentHistory.size() > 1) {
// remove the last (==current) fragment from history
mFragmentHistory.remove(mFragmentHistory.size() - 1);
// get new last fragment from history - this is now the one to load
Fragment loadFragment = mFragmentHistory.get(mFragmentHistory.size() - 1);
// load this fragment, don't add it to the history
loadFragment(loadFragment, false);
} else {
// perform default system back or implement your custom handler here
super.onBackPressed();
}
}
You will have to consider maintaining state over Activity restarts (screen rotation etc.).
Related
I'm setting a new android app, and I want to exploit the bottom navigation bar in my phone (previous,home) so that i don't have to create a customized one, I want to know if it is possible to set an onClickEvent on these buttons
You have to handle both button separately.
For hardware back button (Previous) you have to override onBackPress method in your Activity class. So, you will get the back press event you can change the behavior of back button if you don't want back event just remove super.onBackPress(). If you want to do this from fragment you just need to add your logic like you can get current fragment instance from FragmentManager like below and call fragment method what ever you want to do on that fragment.
\\ To get current fragment
\\ NOTE: I add back stack name as class name.
public static Fragment getCurrentFragment(FragmentManager fragmentManager) {
int count = fragmentManager.getBackStackEntryCount();
if (count > 0) {
FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1);
String tag = backStackEntry.getName();
return fragmentManager.findFragmentByTag(tag);
}
return null;
}
#Override
public void onBackPressed() {
// enter code here
super.onBackPressed(); // Remove this line if you don't want to go back.
}
For home button handling you can use this answer or this post.
Please comment me if you have more questions.
I have an Activity whose layout is a fragment which can be filled by different Fragments classes. The thing is that when I am in Fragment X and I press back, I would like to override onBackPressed method in order to execute a method asking the user whether to save input data. However, the problem is that onBackPressed can only be overwritten from the activity, then my question is:
Should I create a public method in Fragment X and call it from the overwritten onBackPressed method or should I use interfaces or whatever else?
I already checked other related posts like how to move back to the previous fragment without loosing data with addToBackStack, but I think this is a different question..
When I wanted to do something similar, I created tags for the fragments and a Fragment object in the parent activity named mCurrentFragment. Every time I would load a fragment, I assigned it to mCurrentFragment. So I would override the onbackPressed() from my activity and then check the fragment instances:
#Override
public void onBackPressed() {
if (mCurrentFragment instanceof FragmentA) {
//Do something
//e.g. mCurrentFragment.save();
} else if (mCurrentFragment instanceof FragmentB) {
//Do something else
} else {
super.onBackPressed()
}
}
If you want to call a method from a fragment, you just use the Fragment object you created (in my case mCurrentFragment) and get access to all of its public methods (as in the example for FragmentA above)
§EDITED to include code from the comments
So i have a working solution, it seems not to be ideal but just need to confirm - so i have an activity with 3 fragments showing one at a time - i need to block the default back operation for two of those fragments so that when the back button is pressed nothing happens(for now, still developing).
So what i did was that i used an interface
//top of activity snippet
public class StudyKitActivity extends AppCompatActivity implements OnStudyKitBackListener{
private int fragment_id;
.
.
.
.
//interface method
#Override
public void onBackChanged(int fragment_id) {
this.fragment_id = fragment_id;
}
to set a variable on the activity
when i am changing the current fragment in the activity e.g set 1 for fragment A, 2 for fragment B and 3 for fragment C, so this is code snippet within fragment 2 when moving to fragment 3 -
onStudyKitBackListener.onBackChanged(3);
StudyKitResultFragment studyKitResultFragment = StudyKitResultFragment.getInstance(examResult);
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.frame_container, studyKitResultFragment).addToBackStack("exam_result").commit();
and i did an overwrite in my activity's onBackpressed to check the value of that variable and either execute the default OnBackPressed or do nothing like so -
#Override
public void onBackPressed() {
if(this.fragment_id==2||this.fragment_id==3){
}
else{
super.onBackPressed();
}
}
i came up with this cos other solutions like setting a tag and trying to get the fragment's tag or by checking current fragments id did not work for me, this is working like i expected but i have not seen it anywhere just want to know if there is an alternate solution.
I have my MainActivity which has 6 fragments in the Navigation Drawer. Now, whenever I am in any of the 6 fragments and if I press back button, my app is exiting. I want to exit only from the 1st fragment. If I am in other fragments, then if I press back, I want it to come to the 1st fragment and from there if I press back again, then I want to exit.
I have to replace the fragments to the 1st fragment when it is not in the 1st position. I know that. But how exactly can I implement the whole in onBackPressed ?
Please help !! Thanks in advance.
override the onBackPress method and add this code;
public void onBackPressed(){
int count = getFragmentManager().getBackStackEntryCount(); // if stack count is 0, it means no fragment left to pop back stack
if (count = 0) {
finish();
}
}
You can get the fragment name with the instance and we can check weather it is in home fragment or not. Paste this code in the onBackPressed method in main activity class.
Fragment f = getSupportFragmentManager().findFragmentById(R.id.frame_container);
/**
* Compare the instances based on fragment name to change the action bar
*/
if (f instanceof HomeFragment) {
finish();
System.exit(0);
} else {
super.onBackPressed();
}
I have followed the official documentation http://developer.android.com/guide/practices/tablets-and-handsets.html for Tablet support to create dual pane layout that works as shown below, in that in small screens (phones) it uses one Fragment inside one Activity to display a list of objects and another fragment inside another Activity.
Every other documentation I read talks about a one way flow from Master to details, now I want to go back the other way, from details to master and I am stuck.
In the details, I have added an Item that I want to display in the list and I want this to be dynamic such that I can add few items and each time I hit save I want the List to grow.
This is what I have done so far
In FragmentA(List Fragment) I have a method that (re)loads the data and call notifyDataSetChanged on the adapter.
I added a method in the call back that is called each time an item is added. And both Activity A and Activity B implements this listener
So when I add an item in FragmentB(Details Fragment) I call the listener and on Activity A which is housing the dual pane layout I try this
public void OnNewCustomerAdded() {
Fragment frag = null;
frag = getSupportFragmentManager().findFragmentByTag("CustomertListFragment");
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.detach(frag);
ft.attach(frag);
ft.commit();
}
Unfortunately that throws a NPE, and also if I call the methods directly in the Fragment to reload data, that throws an NPE. The only thing that works with some side effects is this
public void OnNewClientAdded() {
Intent intent = getIntent();
finish();
startActivity(intent);
}
So how can I safely restart a Fragment inside an Activity without restarting the other Fragment.
Wow, this is quite tricky, never expected it to be this challenging. Well this is how I solve it.
First I removed the second activity and reworked the code to show single pane in handheld devices and dual pane in tablets using just one Activity instead of two. This is not necessary but it helps when you are dealing with one set of lifecycles and listeners.
Then to actually have items added in the DetailsFragment appear immediately in the ListFragment while still having the Details Fragment open. I finished the containing Activity, restarted it and passed it an intent that tells it to start up the DetailsFragment
Remember that the ListFragment is set to start up no matter which device size. So you just need to start the DetailsFragment and since there is already a call back that does that it makes it easy so here is the code
//Callback method for when an item is added, called from the Details
//Fragment
public void OnNewCustomerAdded() {
Intent mIntent = getIntent();
mIntent.putExtra(Constants.SHOULD_START_CUSTOMER_DETAILS, true);
finish();
startActivity(mIntent);
}
Then in the onCreate of the Activity, after the ListFragment has been started, you do this
boolean shouldStartCustomerDetails = getIntent().getBooleanExtra(Constants.SHOULD_START_CLIENT_DETAILS, false);
if (shouldStartClientDetails){
OnCustomerListItemSelected(0);
}
The OnCustomertListItemSelected is the standard mCallback listener that you get if you created a Master/Details Activity in Android Studio, I just modified it to suit my app like so
/**
* Callback method from {#link OnCustomerListItemSelectedListener}
* indicating that the item with the given ID was selected.
*/
#Override
public void OnCustomerListItemSelected(long id) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
CustomerDetailsFragment fragment = CustomerDetailsFragment.newInstance(id);
getSupportFragmentManager().beginTransaction()
.replace(R.id.customeractivity_detail_container, fragment)
.commit();
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
CustomerDetailsFragment fragment =
CustomerDetailsFragment.newInstance(getIntent().getLongExtra(Constants.ARG_ITEM_ID, 0));
getSupportFragmentManager().beginTransaction()
.add(R.id.customeractivity_list_container, fragment)
.commit();
}
}