Load the same Fragment with different data - android

I had a fragment called DomaineDashboardFragment, if it's the current fragment and I opened it with a different data in the bundle, it's not refreshed, it keeps the old data, Does anyone have an idea to resolve that ?
the fragment works fine, the problem is just when I change data.

To everyone faces the same probleme, you can do this :
DomaineDashboardFragment currentFragment = (DomaineDashboardFragment) getSupportFragmentManager().findFragmentByTag("DomaineDashboardFragment");
if(currentFragment != null && currentFragment.isAdded())
((MyApplication)getApplication()).removeFragment(MainActivity.this, currentFragment);
Handler h = new Handler();
h.postDelayed(new Runnable() {
#Override
public void run() {
DomaineDashboardFragment domaineDashboardFragment = new DomaineDashboardFragment();
domaineDashboardFragment.setArguments(b);
((MyApplication)getApplication()).setUpFragment(MainActivity.this, domaineDashboardFragment, R.id.fragment_container);
}
}, 100);
remove fragment and then attach the new one

You can use the latest Android Architecture components and create a ViewModel class. So, what you can do it, whenever you get data in bundle in Fragment, set the data to ViewModel and notifyChange();
Other than this, you must detach and attach the fragment again to get the new data.

Related

Could not find active fragment with index -1

Activity fragment manager problem When change orientation:
Caused by: java.lang.IllegalStateException: Could not find active fragment with index -1
at
android.support.v4.app.FragmentManagerImpl.restoreAllState(FragmentManager.java:3026)
at
android.support.v4.app.Fragment.restoreChildFragmentState(Fragment.java:1446)
at
android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1380)
at
android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1740)
at
com.motors.mobile.core.v2.DaggerIncludeBaseActivity.onCreate(DaggerIncludeBaseActivity.java:26)
Follow my code :
#Override
protected void tabletPortraitInit(Bundle savedInstanceState) {
super.tabletPortraitInit(savedInstanceState);
openSubFragment();
}
#Override
protected void tableLandscapeInit(Bundle savedInstanceState) {
super.tableLandscapeInit(savedInstanceState);
openSubFragment();
}
protected void openSubFragment() {
Bundle bundle = getIntent().getBundleExtra(CAR_DETAIL_KEY);
fragment = new BuyDetailFragment();
if (getSupportFragmentManager().findFragmentByTag(BuyDetailFragment.TAG) != null)
fragment = (BuyDetailFragment) getSupportFragmentManager().findFragmentByTag(BuyDetailFragment.TAG);
fragment.setArguments(bundle);
menuClickListener = fragment;
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.flMain, fragment, BuyDetailFragment.TAG)
.commit();
// init toolbar items
View tbView = getLayoutInflater().inflate(R.layout.items_detail_menu_layout, toolbar.findViewById(R.id.container), true);
phone = tbView.findViewById(R.id.phone);
message = tbView.findViewById(R.id.message);
link = tbView.findViewById(R.id.notifications);
site = tbView.findViewById(R.id.site);
shortlistView = tbView.findViewById(R.id.wishListMenu);
phone.setOnClickListener((e) -> menuClickListener.clickPhone());
message.setOnClickListener((e) -> menuClickListener.clickMessage());
link.setOnClickListener((e) -> menuClickListener.clickNotifications());
site.setOnClickListener((e) -> menuClickListener.clickSite());
shortlistView.setOnClickListener((e) -> menuClickListener.clickShortlist());
Drawable drawable = ContextCompat.getDrawable(this, R.drawable.vector_heart);
drawable.setColorFilter(ContextCompat.getColor(this, R.color.colorAccent), PorterDuff.Mode.SRC_IN);
shortlistView.changeIcon(drawable);
}
There is no my baseActivity
Does your fragment have setRetainInstance(true)? If so, that may be causing you an issue here, especially if you are using a fragment apart of FragmentStatePagerAdapter.
This can happen with a combination of dismissAllowingStateLoss after onSaveInstanceState and retainInstanceState.
See this helpful example with steps to reproduce (that site does not allow commenting, but it helped me diagnose the issue)
Steps to reproduce:
Open page and show dialog fragment with retainInstance = true
Background app, onSaveInstanceState is called
dismiss dialog in an async task via dismissAllowingStateLoss
perform configuration change, for example by changing language or orientation
open app
crash "Unable to start activity... java.lang.IllegalStateException: Could not find active fragment with index -1"
Under the scenes what's going on is that FragmentManagerImpl.restoreAllState now has an active fragment with an index of -1 because dismissAllowingStateLoss removes the fragment from the backstack, BUT, it is still part of nonConfigFragments because the commit part of dismissAllowingStateLoss was ignored as it was called after onSaveInstanceState.
To fix this will require one of:
not using retainInstanceState on Dialogs that can be dismissed via dismissAllowStateLoss, or
not calling dismiss after state loss
and implementing the desired behavior in a different way.

Showing fragment after activity fetches data

I'm fetching data in my activity that is needed by several fragments. After the data is returned, I create the fragments. I was doing this via an AsyncTask, but it led to occasional crashes if the data returned after a screen rotation or the app is backgrounded.
I read up and thought the solution to this was instead using an AsyncTaskLoader. Supposedly it won't callback if your activity's gone, so those errors should be solved. But this now crashes every time because "Can not perform this action (add fragment) inside of onLoadFinished".
How am I supposed to handle this? I don't want my fragments to each have to fetch the data, so it seems like the activity is the right place to put the code.
Thanks!
Edit 1
Here's the relevant code. I don't think the problem is with the code per-se, but more of my whole approach. The exception is pretty clear I shouldn't be creating fragments when I am. I'm just not sure how to do this otherwise.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportLoaderManager().initLoader(BREWERY_LOADER, null, this).forceLoad();
}
//================================================================================
// Loader handlers
//================================================================================
#Override
public Loader<Brewery> onCreateLoader(int id, Bundle args) {
int breweryId = getIntent().getIntExtra(EXTRA_BREWERY_ID, -1);
return new BreweryLoader(this, breweryId);
}
#Override
public void onLoadFinished(Loader<Brewery> loader, Brewery data) {
if (data != null) {
onBreweryReceived(data);
} else {
onBreweryError();
}
}
#Override
public void onLoaderReset(Loader<Brewery> loader) {
}
...
protected void onBreweryReceived(Brewery brewery) {
...
createFragments();
}
...
protected void createFragments() {
FragmentManager fm = getSupportFragmentManager();
//beers fragment
mBeersFragment = (BreweryBeersFragment)fm.findFragmentById(R.id.beersFragmentContainer);
if (mBeersFragment == null) {
mBeersFragment = new BreweryBeersFragment();
fm.beginTransaction()
.add(R.id.beersFragmentContainer, mBeersFragment)
.commit();
Bundle beersBundle = new Bundle();
beersBundle.putInt(BreweryBeersFragment.EXTRA_BREWERY_ID, mBrewery.getId());
mBeersFragment.setArguments(beersBundle);
}
}
Edit 2
My new strategy is to use an IntentService with a ResultReceiver. I null out callbacks in onPause so there's no danger of my activity being hit when it shouldn't be. This feels a lot more heavy-handed than necessary, but AsyncTask and AsyncTaskLoader neither seemed to have everything I needed. Creating fragments in those callback methods doesn't seem to bother Android either.
From the MVC (Model -- View -- Controller) viewpoint, both the Activity and its fragments are Controller, while it is Model that should be responsible for loading data. As to the View, it is defined by the layout xml, you can define custom View classes, but usually you don't.
So create a Model class. Model is responsible for what must survive a screen turn. (Likely, it will be a static singleton; note that Android can kill and re-create the process, so the singleton may get set to null.) Note that Activities use Bundles to send data to themselves in the future.

How to properly remove retained instance Fragment

Currently, I would like to retain an expensive data structure, during configuration changes. I choose not to use Bundle to handle it, as the expensive data structure is not parcelable.
Hence, I use a non-UI Fragment (Called it RetainInstanceFragment), with its setRetainInstance(true) to hold the data structure.
public class RetainInstanceFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Creating expensive data structure
expensiveDataStructure = CreateExpensiveDataStructure();
// Tell the framework to try to keep this fragment around
// during a configuration change.
setRetainInstance(true);
}
public ExpensiveDataStructure expensiveDataStructure = null;
}
An UI Fragment (Called it UIFragment) will get the expensive data structure from RetainInstanceFragment. Whenever there is configuration changes on UIFragment, UIFragment will always try to get the "cached" RetainInstanceFragment from FragmentManager, before it decides to create a new RetainInstanceFragment.
Example code is as follow.
public class UIFragment extends SherlockListFragment
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FragmentManager fm = getFragmentManager();
// Check to see if we have retained the worker fragment.
retainInstanceFragment = (RetainInstanceFragment)fm.findFragmentByTag("data");
// If not retained (or first time running), we need to create it.
if (retainInstanceFragment == null) {
retainInstanceFragment = new RetainInstanceFragment();
fm.beginTransaction().add(watchlistArrayFragment, "data").commit();
} else {
// We can re-use retainInstanceFragment.expensiveDataStructure even
// after configuration change.
}
}
}
However, there's a problem. Whenever I destroy my old UIFragment, and replace it with new UIFragment, I expect old RetainInstanceFragment will be destroyed as well. Here is how I destroy and create new UIFragment
public class MyFragmentActivity extends SlidingFragmentActivity
// Being triggered when there is different menu item in sliding menu being
// selected.
public void selectActiveContent(Country country) {
Fragment fragment = new UIFragment(country);
getSupportFragmentManager().beginTransaction().replace(R.id.content, fragment).commitAllowingStateLoss();
}
But old RetainInstanceFragment is never destroyed.
My guess is, perhaps I forget to perform clean up in UIFragment. Hence, I add the following code
UIFragment
#Override
public void onDetach() {
super.onDetach();
// To differentiate whether this is a configuration changes, or we are
// removing away this fragment?
if (this.isRemoving()) {
FragmentManager fm = getFragmentManager();
fm.beginTransaction().remove(retainInstanceFragment).commit();
}
}
However, it doesn't work all the time. I perform several sliding menu clicks.
1. selectActiveContent() -> Create new UIFragment and new RetainInstanceFragment
2. selectActiveContent() -> Create new UIFragment, but re-use previous RetainInstanceFragment. (Wrong behavior)
3. selectActiveContent() -> Create new UIFragment, and new RetainInstanceFragment.
4. selectActiveContent() -> Create new UIFragment, but re-use previous RetainInstanceFragment. (Wrong behavior)
Any idea how I can properly remove retained instance Fragment?
As suggested by #Luksprog, the following method works. However, it still do not explain why the previous cleanup done through onDetach doesn't work. If anyone can explain why this solution works and previous doesn't, I would be very thankful. :)
UIFragment
#Override
public void onDetach() {
super.onDetach();
}
public void cleanupRetainInstanceFragment() {
FragmentManager fm = getFragmentManager();
fm.beginTransaction().remove(this.retainInstanceFragment).commit();
}
MyFragmentActivity
public class MyFragmentActivity extends SlidingFragmentActivity
// Being triggered when there is different menu item in sliding menu being
// selected.
public void selectActiveContent(Country country) {
// *******************************************
// Solution suggested by #Luksprog. It works!
// But I have no idea why it works and previous doesn't work...
// *******************************************
Fragment oldFragment = getSupportFragmentManager().findFragmentById(R.id.content);
if (oldFragment instanceof UIFragment) {
((UIFragment)oldFragment).cleanupRetainInstanceFragment();
}
Fragment fragment = new UIFragment(country);
getSupportFragmentManager().beginTransaction().replace(R.id.content, fragment).commitAllowingStateLoss();
}
(Edited) Useful comment by #Luksprog
The fragment transactions are not made right away. My assumption was
that doing that transaction in the onDetach() callback will not remove
the retain fragment instance before the UI fragment's replace
transaction finished and so your new UI fragment will still see the
retain fragment instance still available, so it will not create a new
one. Your previous method is not in the spirit of the fragments
framework where fragments are unaware of other fragments and the
activity manages all of them as it knows more about the overall
application state.
I think you can just remove the fragment from fragment transaction.
if (mWorkFragment != null) {
fm.beginTransaction().remove(mWorkFragment).commitAllowingStateLoss();
}

Fragment getActivity() returns null in Activity JUnit test

I wrote Android JUnit test for Activity that instantiates fragments (actually tabs). During the test, when I try to do anything with these tabs, they crash because getActivity() method in them returns null. The actual application (not a test) never shows this behavior and fragment getActivity() always returns the right parent activity there. My test case looks like:
public class SetupPanelTest extends ActivityUnitTestCase<MyAct> {
FSetup s;
public SetupPanelTest() {
super(MyAct.class);
}
protected void setUp() throws Exception {
super.setUp();
startActivity(new Intent(), null, null);
final MyAct act = getActivity();
AllTabs tabs = act.getTabs();
String tabname = act.getResources().getString(R.string.configuration);
// This method instantiates the activity as said below
s = (FSetup) tabs.showTab(tabname);
FragmentManager m = act.getFragmentManager();
// m.beginTransaction().attach(s).commit();
// ... and even this does not help when commented out
assertTrue(s instanceof FSetup); // Ok
assertEquals(act, s.getActivity()); // Failure
}
public void testOnPause() {
// this crashes because s.getActivity == null;
s.onPause();
}
}
The AllTabs creates a fragment, then required, in this way:
FragmentManager manager = getFragmentManager();
Fragment fragment = manager.findFragmentByTag(tabname);
if (fragment == null || fragment.getActivity() == null) {
Log.v(TAG, "Instantiating ");
fragment = new MyFragment();
manager.beginTransaction().replace(R.id.setup_tab, fragment, tabname).commit();
....
Here, all fragments are initially placeholders that are later replaced by the actual fragments:
<FrameLayout
android:id="#+id/setup_tab"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
The logcat shows that the new fragment has been instantiated. In the same layout, there is also the previously mentioned AllTabs fragment that seems not having this problem (where and how it gets FragmentManager otherwise):
<TabWidget
android:id="#android:id/alltabs"
...
Most impressively, when I call attach directly on the fragment manager obtained on the right activity, this still has no effect. I tried to put five seconds delay (I have read that transaction may be delayed), I tried to call the rest of the test through runOnUiThread - nothing helps.
The question is that is need to do so to attach my fragments to the activity also during the test. I have fragment and I have activity, I cannot attach one to another.
Even if you call .commit() on transaction, it is still not done, fragments are attached only lazily.
FragmentManager m = activity.getFragmentManager();
m.executePendingTransactions();
This finally attaches all fragments to the activity. Seems redundant when running the application itself but required in JUnit test case.

Refreshing my fragment not working like I thought it should

I have this nice method in my ListFragment I call to fill out the details of my other fragment:
private void showClientDetails(int pos) {
myCursor.moveToPosition(pos);
int clientId = myCursor.getInt(0);
if(mIsTablet) {
// Set the list item as checked
getListView().setItemChecked(mCurrentSelectedItemIndex, true);
// Get the fragment instance
ClientDetails details = (ClientDetails) getFragmentManager().findFragmentById(R.id.client_details);
// Is the current visible recipe the same as the clicked? If so, there is no need to update
if (details == null || details.getClientIndex() != mCurrentSelectedItemIndex) {
// Make new fragment instance to show the recipe
details = ClientDetails.newInstance(mCurrentSelectedItemIndex, clientId, mIsTablet);
// Replace the old fragment with the new one
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.client_details, details);
// Use a fade animation. This makes it clear that this is not a new "layer"
// above the current, but a replacement
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
}
}
It is called when the user clicks on a client in the ListFragment view:
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
mCurrentSelectedItemIndex = position;
showClientDetails(position);
}
This works great, but then another FragmentActivity can change the data that this displays so I thought this would work:
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
//update the client list incase a new one is added or the name changes
if(requestCode==1899)
{
myCursor.requery();
theClients.notifyDataSetChanged();
showClientDetails(mCurrentSelectedItemIndex); //now if client was edited update their details in the details fragment
}
}
Now I know the line:
if (details == null || details.getClientIndex() != mCurrentSelectedItemIndex) {
Prevents the block of code being reached when its called in my onActivityResult. So if I remove that if statement, then things freak out and the ft.commit() has a hissy fit and gives me the error:
`07-08 16:53:31.783: ERROR/AndroidRuntime(2048): Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
So I guess what I am trying to do isn't as cut and dry as it sounds, it makes no sense to me since I can do that onListItemClicked all day and the fragment displays the details of the newly clicked client constantly very well...
I even tried this in my onActivityResult:
//simulate a click event really fast to refresh the client details
showClientDetails(0);
showClientDetails(mCurrentSelectedItemIndex);
that does nothing, is it im trying to call something from the onActivityResult which isn't a Ui thread or what not?
I do have this in my code of the ListFragment as well
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("currentListIndex", mCurrentSelectedItemIndex);
}
Is this what the error is complaining about?
The other FragmentActivity's action is performing a task which requires the Fragment to save its state via a call to onSaveInstanceState in preparation for a new instance being reconstructed. I've seen this for example when I was firing off an activity from a fragment which filled the entire screen as this resulted in the view being detached from the fragment, state needing to be saved etc.
You basically cannot call commit between onSaveInstanceState and the new instance of the fragment being recreated. See commit.
As for the solution, then either re-think to try and avoid the commit being called when it is or alternatively call commitAllowingStateLoss if you think it's OK for the UI to change unexpectedly on the user.
I think the answer is to not do any fragment transactions in the onActivityResult. I believe what happens is that when the onActivityResult is called the activity it is in hasn't yet resumed and restarted its fragments.
Use a handler to post back the function call to the activity.
handler.post(new Runnable() {
#Override
public void run() {
showClientDetails(mCurrentSelectedItemIndex);
}
});
you can do an other thing which no very good but it works.
finish (you activity)
do an intent of that same class by :
Intent mIntent = new Intent(getApplicationContext(), YouClass.class);
startActivity(mIntent);

Categories

Resources