Best way to use Fragments - android-fragments

There are a lot of answers of this questions but technically my question is different, I've read some documentation and agreed we should not use multiple fragments in only one activity , we should use multiple activities with multiple fragments depend on project requirement or UI specification ,
so architecture of my application is
1. BaseActivity and all activities extends from that
2. BaseFragment and all Fragments extends from that
my Code:
in BaseActivity I've made following functions
public void replaceFragment(int containerViewId, Fragment fragment, String fragmentTag, boolean addToBackStack) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(containerViewId, fragment, fragmentTag);
if(addToBackStack)
fragmentTransaction.addToBackStack(fragmentTag);
if(fragmentTag.matches("") && addToBackStack)
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
To Add Fragment in Framelayout
public void addFragment(int containerViewId, Fragment fragment, String fragmentTag, boolean addToBackStack, boolean isPending) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(containerViewId, fragment, fragmentTag);
if(addToBackStack)
fragmentTransaction.addToBackStack(fragmentTag);
if(fragmentTag.matches("") && addToBackStack)
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
if(isPending)
fragmentManager.executePendingTransactions();
}
Back Press Functionality
public void popBackStackImmediate() {
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.popBackStackImmediate();
}
Get Total Fragments
public List<Fragment> fragmentList(){
return getSupportFragmentManager().getFragments();
}
and the flow is
FragmentA (add) -> FragmentB (replace) ->
QUETSION 1 : why totalFragments are showing 2 ? while I've replaced FragmentB to A
QUESTION 2: when i press back button still showing 2 Fragments and how may i remove from stack ?
Question 3: FragmentB is visible but FragmentA fragment life cycle working too ..means onResume and onCreate funcations are calling too strange but true dont know why

Question 1: You have not removed any fragment therefore the fragment count is 2. When you replace a fragment, you are replacing it in the fragment container, not it's instance.
Question 2: Once you add a fragment to the backstack, it means you will navigate back to it once you hit the back button. Still, your fragment manager fragment count will still be 2 since no fragment was removed. If you want to remove it the following code will do the trick:
getSupportFragmentManager().beginTransaction().remove(yourFragment).commit();
Question 3: Once you replace fragment A with fragment B, fragment A will still be active and will call it's onPause, onDestroy and onDestroyView, if you bring it back to the container it starts it's lifecycle from onCreateView, that meaning that running onResume is normal, however onCreate is not.

Related

Start a fragment from MainActivity that calls another fragment. The listener returns to the MainActivity

My MainActivity(1) implements FragmentStatePagerAdapter, with SectionPagerAdapter and ViewPager. It works OK, apart from the problem I have to call getItemPosition to update one of the Fragments, which causes the whole thing to be recreated. Anyway...
One of the "tabs", calls a Fragment(2=BaixarOrcamentoFragment.java), which in turn, calls another Fragment(3=FillReasonToBaixaFragment.java), so the user can insert a text.
Fragment(2) implements THE LISTENER that Fragment(3) uses to return a "text value", so Fragment(2) can continue and finish it's tasks.
Here is the code in Fragment(2), that calls Fragment(3):
FragmentManager fragmentManager = getFragmentManager();
FillReasonToBaixaFragment fillFragment = new
FillReasonToBaixaFragment();
Bundle args = new Bundle();
args.putInt("ORCID", baixarModelList.get(masterPosition)
.getOrcGroupID());
fillFragment.setArguments(args);
fillFragment.show(fragmentManager, FILL_REASON_TO_BAIXA);
Then, Fragment(3) gets the bundle ORCID,stars the listener, get some data, shows a text input, and finishes by sending this "text" to the interface:
BaixaItemdoOrcamentoListener listener = (BaixaItemdoOrcamentoListener) this
.getContext();
..and then returning what has collected (text) using this interface (listener):
public interface BaixaItemdoOrcamentoListener
{
void OnFinishedFillReason(String mEditext);
}
However, it's not returning back to Fragment(2), which called Fragment(3), where I implemented the method to receive this returning value:
#Override
public void OnFinishedFillReason(String mEditext)
{}
It shows a cast error, saying that .MainActivity cannot be cast to .FillReasonToBaixaFragment$BaixaItemdoOrcamentoListener
I went on and DECLARED the OnFinishedFillReason inside the MainActivity, which implements FillReasonToBaixaFragment.BaixaItemdoOrcamentoListener.
Be aware now, that the actual implementation of the tasks are in Fragment(2).
Guess what: when I enter the text in Fragment(3) and press ( android:imeOptions="actionDone"), it returns to the MainActivity, NOT to the Fragment(2), the one that has called Fragment(3).
MainActivity doesn't know the existence of any of the the Views inside Fragment(2), a priori, which will, in turn, update all these views ONCE received the "text" from Fragment(3).
Perhaps I didn't search thoroughly, but I couldn't find anything that resembles this situation.
How can I make it happen?
Afternoon everyone.
I figured it out how to make this happen.
First: I removed OnFinishedFillReason interface from Fragment(2), and therefore, from Fragment(3).
In Fragment(2), I started the Fragment(3) like this, setting a setTargetFragment:
FragmentManager fragmentManager = getFragmentManager();
FillReasonToBaixaFragment fillFragment = new
FillReasonToBaixaFragment();
fillFragment.setTargetFragment(BaixaOrcamentoFragment.this,
FILL_REASON_TO_BAIXA);
Bundle args = new Bundle();
args.putInt("ORCID", baixarModelList.get(masterPosition)
.getOrcGroupID());
fillFragment.setArguments(args);
fillFragment.show(fragmentManager,
this.getClass().getSimpleName());
The target fragment is the Fragment(2) itself.
Then I added in Fragment(2):
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (requestCode == FILL_REASON_TO_BAIXA && resultCode == Activity.RESULT_OK)
{// code in here }
In Fragment(3), I added in the onCreate(Bundle savedInstanceState) method, the following:
targetFragment = getTargetFragment();
if (targetFragment instanceof BaixaOrcamentoFragment)
{
orcID = getArguments().getInt("ORCID");
}
At the end of the returning elements, like this:
Intent returnData = new Intent();
Bundle bundle = new Bundle();
bundle.putString("EDITTEXT", txtEdited);
returnData.putExtras(bundle);
targetFragment.onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, returnData);
this.dismiss();
return true;
And then, inside the onActivityResult I collected the data, like this:
Bundle returnedValues = data.getExtras();
String mRazao = returnedValues.getString("EDITTEXT").trim();
And that was it.
But I still have a problem: I have 6 tabs. Tab 4 is never selected as currentItem, inside the method below:
#Override
public void onPageSelected(int position)
{
mViewPager.setCurrentItem(position);
}

Issue with ViewPager and FragmentStatePagerAdapter when activity is destroyed

I have an activity which creates viewpager adapter (FragmentStatePagerAdapter) and sets the viewpager to this adapter.
This is how viewpager adapter is created and is set to the viewpager widget.
viewPageAdapter = new viewPageAdapter(getSupportFragmentManager(), getApplicationContext());
mCustomSwipeViewPager.setAdapter(viewPageAdapter);
And this is how i am creating viewPageAdapter
public class viewPagerAdapter extends FragmentStatePagerAdapter {
private SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
public viewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// returning newly created fragment
}
#Override
public int getCount() {
// returning total count
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
// This is where i am putting created fragment in the sparse array,
// which i will be accessing in the activity based on position
// for updating fragment views.
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
// And here i am removing it from the sparse array
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
}
Now, i went to the developers options and checked "Donot keep activities".
This is where the problem starts, after moving forward this activity, this activity will get destroyed, which is fine.
Now if i come back, the a new adapter is created in onCreate() and is set to the viewpager, but it's not calling getItem() again, instead it's calling instantiateItem(), with fragment in memory as null.
I checked the fragmentmanager in debug mode, the fragment manager holds two fragments as activestate, with all fragment fields as null.
Things i have tried...
Tried clearing the fragment manager in onDestroy() of activity using
fragmentManager.getFragments.clear() but no luck.
Made getItemCount() of adapter to 0 and called notifydatasetChanged() on adapter in onDestroy(), still getting the same.
However, if comment out the super.onSavedInstance() of activity, it's working fine, but this is not i want as it's failing for few cases.
Is there any way wherein if i create a new adapter and again set it to the viewpager, it should start afresh, i.e should call getItem() from first position without minimizing the performance of fragmentStateViewPagerAdapter.
Any kind of help or suggestion would be appreciated.
The only solution i figured out is to comment out the default super.onSaveInstanceState(outState) inside onSaveInstanceState(Bundle outState) callback of activity lifecycle.
#Override
protected void onSaveInstanceState(Bundle outState) {
// super.onSaveInstanceState(outState);
}
Doing so will not keep the fragment old instance in the fragmentManager of viewpager and will start afresh.
I couldn't thought of any better solution, as there are many global variables inside fragment and in also in activity and it's presenter, and saving them in the onSaveInstanceState bundle and them restoring them in onCreate or in onRestoreInstanceState() would be very heavy for me.
Any better solution or approach than this is still appreciable.
Had the same issue, was assigning the fragment's view container an id programmatically, we removed this and assigned an id in its layout XML file and the issue went away.

Duplicate context menu icons when launching the app from background

I am testing my app with "Don't Keep Activities" options from developer options. When I try to background the app and launch it , am getting multiple duplicate icons added to the actionbar.
I am adding the context menu icon from the fragment. I have a string passed in a bundle to the fragment. When I background and launch the app, Android tries to recreate the activity and as setHasOptionsMenu(true); is called multiple time during this process and adds duplicate icons to the actionbar.
The following fixes this issue , but would like to know if this is the best approach
if(savedInstanceState == null) {
setHasOptionsMenu(true);
}
Maybe i'm a little late to answer your question, but it can be useful for future users.
The problem origin:
When you background the app and and launch it, the system will save and restore the state of your fragment. Also onCreate() method will be called and a new fragment will be added to your Activity, and you end up with multiple fragments running in your activity, each one added an item to the menu.
The solution:
(+) The correct fix: simply add a null check in your onCreate() method before adding your fragment (to avoid duplication)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
if (savedInstanceState == null) {
Fragment fragment = ...
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
}
(+) Or a workaround1: Clear your menu inside onCreateOptionsMenu() method of your fragment
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.your_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
(+) Or a workaround2: Call setHasOptionsMenu(true) only when savedInstanceState == null like you mentioned in OP.

Calling a non-parental Activity method from fragment without creating a new instance

I have my MainActivity and inside that I have a number of fragments. I also have another activity that works as my launcher and does everything to do with the Google Drive section of my app. On start up this activity launches, connects to Drive and then launches the MainActivity. I have a button in one of my fragments that, when pushed, needs to call a method in the DriveActivity. I can't create a new instance of DriveActivity because then googleApiClient will be null. Is this possible and how would I go about doing it? I've already tried using getActivity and casting but I'm assuming that isn't working because DriveActivity isn't the fragments parent.
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//TODO for test only remove
directory = new Directory(SDCARD + LOCAL_STORAGE);
byte[] zippedFile = directory.getZippedFile(SDCARD + STORAGE_LOCATION + directory.getZipFileName());
//Here I need to somehow call DriveActivity.uploadFileToDrive(zippedFile);
//((DriveActivity)getActivity()).uploadFileToDrive(zippedFile);
}
});
Right, so I'm having a bit of difficulty with the heirarchy but I think what you want to do is define a method in the fragment that the activity will be required to override to use.
This will allow you to press the button, and then fire a method whos actual implementation is inside the parent.
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
public void onItemSelected(String id);
}
example implementation:
private static Callbacks sDummyCallbacks = new Callbacks() {
#Override
public void onItemSelected(String id) {
//Button fired logic
}
};
so in the child you'd do just call:
this.onItemSelected("ID of Class");
EDITED
In retrospect what I believe you need is an activity whos sole purpose is to upload files, not fire off other activities.
Heres an example of a 'create file' activity:Google Demo for creating a file on drive
Heres an example of the 'base upload' activity' Base Service creator

Another "update on popbackstack" topic

There are dozens of this kind of threads, but i couldnt find and solution for my problem (at least working one).
So i have 3 fragments in my viewpager, my last (3rd) fragment is basically a friendlist, i also have a button with which i open a new fragment, where i handle search/request etc. In that fragment i have "back button", i get back to my "3rd" fragment in a viewpager with getFragmentManager().popBackStack(). How can i pass boolean value or something back to the "3rd" fragment?
I tried with manually calling onPause and onResume methods of the 3rd fragment, but then my list is empty. Also no methods of the 3rd fragment is called when i popbackstack.
This is my code
3rd fragment
This is how i open new fragment
ImageButton friendsButton = (ImageButton) v.findViewById(R.id.friendsButton);
friendsButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
SearchActivity configDetailSectionFragment = new SearchActivity();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.item_detail_container, configDetailSectionFragment);
transaction.addToBackStack(null);
transaction.commit();
}
});
and this is how i get back to the 3rd fragment
ImageButton backButton=(ImageButton)rootView.findViewById(R.id.backButton);
backButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getFragmentManager().popBackStack();
} });
My methods for filling exlistview work fine, just no methods are called after popbackstack, so i could update my list.
What should i change and when and where should i call listadapter.notifyDataSetChanged()?
Using add fragment method with addBackStack, on poBackStack previous fragment's onResume will not get called. So if you are using addBackStack and popBackStack, and you want to call onResume of your previous fragment then you have to use replace instead of add. So your code just changes like -
transaction.replace(R.id.item_detail_container,configDetailSectionFragment);
once you did this you can use your onResume method to refresh list.

Resources