We are going to write an app, in one section of app user enter to an Activity that call it PostDetailsActivity.
In this Activity we have a fragment call it PostDetailsFragment. We apply an Interface to communicate between this fragment and activity
public interface changeFragment{
void replaceFragment(Fragment fragment, String backStack);
}
In PostDetailsFragment we have post and it's related posts. related post listed in recyclerview bu an adapter. when user click on Item in adapter, adaoter act like this:
PostDetailsFragment fragment = PostDetailsFragment.newInstance(GsonString);
mListener.replaceFragment(fragment, fragment.getFragmentKey());
and in Activity we act like this:
#Override
public void replaceFragment(Fragment fragment, String backstackName) {
if(fragment instanceof PostDetailsFragment){
fragmentPostDetails = (PostDetailsFragment) fragment;
}
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment);
fragmentTransaction.addToBackStack(backstackName);
fragmentTransaction.commitAllowingStateLoss();
}
What we doing: we create one Activity and if user click on related posts, activity just replace fragment with new instance of PostDetailsFragment with new data.
Issue: after five or more and less, App crashed and got OoutOfMemory Error!
I attache MAT too
How we should manage this problem?
Related
OK I found out that I am opening/replacing the fragment in another layout element than the one the tablayout is in. Therefore the underlying fragment is never really left if i understand correctly and also no lifecycle methods are triggered.
I have FragmentA inside a TabLayout from which i call:
//inside FragmentA
#Override
public void onClickView() {
activity.replaceFragment(FragmentB.newInstance(), true);
}
in replaceFragment the passed fragment is added to the backstack in the transaction and then replaces the fragment
public void replaceFragment(Fragment fragment, boolean doAddToBackStack) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
if (doAddToBackStack) {
String tag = fragment.getClass().toString();
ft.addToBackStack(tag);
}
ft.replace(R.id.mainFragment, fragment);
ft.commit();
}
After that, FragmentB opens, I do my stuff and return via popBackStackImmediate() to FragmentA:
//inside FragmentB
#Override
public void onClickBack() {
activity.getSupportFragmentManager().popBackStackImmediate();
}
and when I go back I need to update something in FragmentA.
According to another post on the platform when returning to a fragment onCreateView() should be called but that does not work for me. According to the fragment lifecycle documentation the onCreateView() is only called the first time the UI is drawn which would explain my troubles.
My question is now what lifecycle method can I use or what other approach should I take to execute code when returning to a fragment from backstack?
According to the following answer you can add a listener to check when it is being called
https://stackoverflow.com/a/52646588/6468214
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// Your Code Here
}
});
Problem 1
I have a Navigation Drawer and most of my fragment transactions happens from here.
So say I have 4 Items in my drawer and I am doing the transaction from all of them. So if I am at the fragment [A] and now I click on the fragment [B], I need to come back to the previous fragment i.e. [A]. But if I keep clicking on the Item B of the navigation drawer that opens the fragment [B], I keep adding it to the backstack and when I press the back button, I am still at the same fragment.
Problem 2
How do I achieve the Clear Top behavior that is used for the intents for the fragments. As intents have the power to clear the activities from the stack from the top only, I want to achieve the same behavior.
Problem 1 & 2 solution Idea:
Create an Interface say FragmentInstanceHandler
public interface FragmentInstanceHandler {
public void openFragment(Fragment fragment, String fragmentTag);
}
Create a BaseFragment like below and extend this to all your Fragment classes:
public BaseFragment extends Fragment {
public FragmentInstanceHandler fragmentInstanceHandler;
public void setFragmentInstanceHandler(FragmentInstanceHandler fragmentInstanceHandler) {
this.fragmentInstanceHandler = fragmentInstanceHandler;
}
}
Implement the FragmentInstanceHandler interface to the Activity in which you are going to open all the Fragments. Let's say Activity is MainActivity:
public MainActivity extends Activity implements FragmentInstanceHandler {
private BaseFragment currentFragment;
#Override
public void openFragment(BaseFragment fragment, String fragmentTag) {
FragmentManager fragmentManager = getSupportFragmentManager();
Fragment oldFragmentInstance = fragmentManager .findFragmentByTag(fragmentTag);
boolean fragmentPopped = fragmentManager.popBackStackImmediate (fragmentTag, 0);
if (!fragmentPopped && oldFragmentInstance == null) {
fragment.setFragmentInstanceHandler(this);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.container, fragment, fragmentTag);
fragmentTransaction.addToBackStack(fragmentTag);
fragmentTransaction.commit();
currentFragment = fragment;
} else if(fragmentPopped ){
currentFragment = oldFragmentInstance;
}
if(mDrawerLayout!= null)
mDrawerLayout.closeDrawers();
}
}
Now whenever you want to open a new Fragment even from any other Fragment you can call method like below, It is advised to provide new tag if you want to have new instance of same Fragment:
fragmentInstanceHandler.openFragment(new MyFragment(), "FragmentNewInstance");
You can tweak FragmentInstanceHandlerto add your own method to replace the current Fragment instead of adding. Above solution just gives you an idea how you can acheive your solution by putting and mananging all your code from one place.
I am trying to do the following use case in Android Fragments. I have 2 fragments.
Fragment A -> Fragment B
When a user does something in Fragment B, I want to have the back stack as follows
Fragment A -> Fragment C. So, when the user presses back I want the user to go back to Fragment A.
I have tried the following
mFragmentManager.popBackStackImmediate();
FragmentTransaction fragmentTransaction = fMgr.beginTransaction()
.replace(R.id.base, Fragment_C, "1")
.addToBackStack(null)
.commitAllowingStateLoss();
The problem here is that I can see Fragment A for a short period of time before Fragment C is shown
If I do the following
mFragmentManager.popBackStackImmediate();
FragmentTransaction fragmentTransaction = fMgr.beginTransaction()
.replace(R.id.base, Fragment_C, "1")
.addToBackStack(null)
.commitNowAllowingStateLoss();
I get the error
This transaction is already being added to the back stack
I can get Fragment C to show up if I do this BUT
mFragmentManager.popBackStackImmediate();
FragmentTransaction fragmentTransaction = fMgr.beginTransaction()
.replace(R.id.base, Fragment_C, "1")
.commitNowAllowingStateLoss();
This works and I don't see Fragment A and see Fragment C but the back button takes the user out of the application. So, is it possible that we can pop the back stack of the fragment and then add another fragment to the back stack w/o showing Fragment A AND the back button takes the user back to Fragment A
Here is an easy method to add fragments to fragments or to adapters within fragments...
from your base activity, make your fragment manager static. assume this activity is called dashboard.
static FragmentManager support;
Don't forget to initialize this in onCreate.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_dashboard);
support = getSupportFragmentManager();
define your new fragment inside your adapter or fragment.
users_item_fragment dialog = new users_item_fragment();
//also, let's add some data...
Bundle args = new Bundle();
args.putString("device", devicesList.get(position));
use the following method to add the fragment easily wherever you would like
//pick an easily remembered tag
public void replace(Fragment fragment, String tag){
FragmentManager man = dashboard.support;
FragmentTransaction fragt = man.beginTransaction();
if(!fragment.isAdded()) {
dashboard.lastTag = dashboard.fragtag;//not needed, but helpful w/ backpresses
fragt.add(R.id.fragment_container, fragment, tag)
.hide(man.findFragmentByTag(fragtag)).commit();
dashboard.fragtag = dashboard.tag;//not needed, but helpful w/ backpresses
}
if(fragment.isAdded() && fragment.isHidden()) {
dashboard.lastTag = dashboard.fragtag;//not needed, but helpful w/ backpresses
fragt.show(fragment);
fragt.hide(man.findFragmentByTag(fragtag)).commit();
dashboard.fragtag = dashboard.tag;//not needed, but helpful w/ backpresses
}
}
To implement this with backpresses working correctly, add this in you onBackPress method of your main activity:
#Override
public void onBackPressed() {
FragmentManager man = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = man.beginTransaction();
fragmentTransaction.hide(getSupportFragmentManager().findFragmentByTag(fragtag))
.show(getSupportFragmentManager().findFragmentByTag(lastTag)).commit();
fragtag = lastTag;// holds the last fragment
}
}
It's easy to see the logic here and easy to manipulate back press events using this.
I have done many programs, where I have implemented multiple Fragments inside a Single Activity, but not when using Single Activity to host multiple Fragments as Tabs and then on Tap show another Fragments...
Using MaterialViewPager library, in which I am calling different different Fragments to show views in their respective Tabs.
Like For the First Tab, I am using two Fragments, where
In First Fragment, I am using RecyclerView... to show list of Menus.
And in Second Fragment, I am using RecyclerView... to show list of Items under particular Menu.
So here my question is How to call Fragment from Fragment.
mRecyclerView.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), mRecyclerView ,new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
Value value = valueList.get(position);
List<Learning> learning = value.getLearning();
// using when putting "item" data into same recyclerview
// but on back press just exiting, not showing list of Menus again
/**
learningAdapter = new LearningAdapter(learning, R.layout.card_learning, getActivity());
mRecyclerView.setAdapter(learningAdapter);
**/
ItemFragment fragment = new ItemFragment();
replaceFragment(fragment);
}
Method replaceFragment
public void replaceFragment(Fragment someFragment) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// using Fragment not Activity, so where can I use frame_container in xml
transaction.replace(R.id.frame_container, someFragment);
transaction.addToBackStack(null);
transaction.commit();
}
you can have a callback interface that is implemented by the activity which hosts these two fragments. Fragment A will use the call back to notify the activity to replace A with fragment B. Depending on your need, you can pass parameters across through the callback method itself.
Your callback interface:
public interface YourCallback {
public void methodWhichReplacesFragmentAwithB(params...);
}
Your Activity hosting fragments:
public class YourActivity extends ... implements YourCallback {
..
..
#Override
public void methodWhichReplacesFragmentAwithB(params...) {
//insert replace fragment code here
}
}
Your fragment will have a callback object, YourCallback callbackObj;. This callback object can be initialised using the activity (pass as this from activity) itself since the activity has the implementation of the interface. Now, you can use
callbackObj.methodWhichReplacesFragmentAwithB(actual_params...);
to replace the fragment. This callback interface can be exploited for other communications to parent Activity as well as other fragment hosted in that activity.
To replace fragment,
FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame_container, someFragment).addToBackStack("null").commit();
You can try this:
YourFragment fragment = new YourFragment();
FragmentTransaction transaction = ((AppCompatActivity) mContext).getSupportFragmentManager()
.beginTransaction();
transaction.replace(R.id.frame, fragment);
transaction.addToBackStack(null);
transaction.commit();
There is One option which i have been using from long time before. I will added the solution with clear example.
So you want to pass the value from fragment A to fragment B
So, here what you want to do is, you have pass the value through the activity.
1) you pass the value from fragment A to activity
2) then activity to fragment B
Step 1: first create an Interface Like below in fragment A
public interface HomePage {
void onHomeButtonsClicked(String clickedButton);
}
public void setOnHomeButtonClicked(HomePage homePage) {
this.homePage = homePage;
}
Step 2: then create the instance like below in Activity. where fragment created in PagerAdapter
((FragmentA) fragment).setOnHomeButtonClicked(new FragmentA.HomePage() {
#Override
public void onHomeButtonsClicked(String buttonClicked) {
pageSelected.onHomeButtonsClicked(selectedPage);
}
}
});
Step 3: then create the interface in Activity like below.
public interface PageSelected {
void onHomeButtonsClicked(String clickedButton);
}
public void setOnHomeButtonClicked(PageSelected pageSelected) {
this.pageSelected = pageSelected;
}
step 3: then add the following method in fragment B in onCreateView or onResume()
((MainActivity) getActivity()).setOnHomeButtonClicked(new MainActivity.PageSelected() {
#Override
public void onHomeButtonsClicked(String clickedButton) {
//fragment B received the value here
}
});
Step 5: finally add the line in fragment A in button click or where you want to pass the value.
homePage.onHomeButtonsClicked("Some String , by this code it is in String. you can change in your own data type");
You can rise your question if you face any difficulties.
when fragment use addToBackStack(null) method and click a method many times in an activity, the fragment page will save to back stack everytime, when I press back key, it will restore the same page, how to add the same fragment to stack just one time?
mSettingBtn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
SettingFragment settingFragment = new SettingFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.replace(R.id.left_framelayout, settingFragment);
transaction.addToBackStack(null);
transaction.commit();
}
});
When adding your Fragment to the backstack you should also pass a TAG to be able to identify that Fragment:
.addToBackStack(SettingsFragment.TAG);
Prior to adding the Fragment you will be able to check if this Fragment is allready in the backstack using :
getFragmentMangager().findFragmentByTag(SettingsFragment.TAG);
This will return null if the Fragment is not allready added.