Android FragmentTransaction commit() - Activity has been destroyed - android

when restarting the app I had problem with getActivity() returning null, so I solved it with onAttach(). However now I have a new problem with FragmentTransaction commit() and commitAllowingStateLoss. It says Activity has been destroyed.
Activity mActivity;
private FragmentActivity myContext;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
myContext =(FragmentActivity) activity;
mActivity = activity;
}
private void navigateToFragment(Fragment fragment){
FragmentTransaction transaction = myContext.getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.content, fragment);
transaction.commitAllowingStateLoss();
}
The navigation works the first time i run the app, but when reopening the app it crashes at transaction.commitAllowStateLoss();
(mActivity).runOnUiThread(new Runnable() {
#Override
public void run() {
enableMenu();
openMenu();
navigateToFragment(new BlankFragment());
}
});
Pls help, don't know what to do...

check whether activity is finishing or not before transacting a fragment(in this case)
Below is the snippet:-
if (!isFinishing()) {
FragmentTransaction transaction = myContext.getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.content, fragment);
transaction.commitAllowingStateLoss();
}

When clicking "Back" and exiting the app, some prosesses will be stored in the memory for faster startup. So to make the app reset a 100% I had to add this in MainActivity.
#Override
protected void onDestroy() {
super.onDestroy();
System.exit(0);
}
If anyone has a better solution please post it!

Related

Android - Talk to an active Fragment from Main Activity

I have a Main Activity with a bunch of Fragments connected to it.
One of the fragments has to be able to recieve data from MainActivity after it has been loaded in the FragmentTransaction and committed.
What is the best way to call a method in that specific fragment?
Do I have to implement a Interface and include it in the MainActivty just for this one fragment? Is there a better way? Can someone point me in the correct direction?
What I have tried now : (Failing at settings ContactsInterface in the MainActivity)
ContactsInterface
public interface ContactsInterface {
void notifyDenied();
void notifyGranted();
}
Fragment
Class.... implements ContactsInterface...
#Override
public void notifyDenied() {
Log.d("DENIED", "DENIED CALLBACK");
}
#Override
public void notifyGranted() {
Log.d("GRANTED", "GRANTED CALLBACK");
}
Main Activity
try {
contactsInterface = (ContactsInterface) this.getApplicationContext();
} catch (ClassCastException e) {
throw new ClassCastException(this.toString()
+ " Needs to implement the methods");
}
Last example throws an ClassCastException.
You need to cast the fragment itself not the Application Context.
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
ContactsInterface contactsInterface = (ContactsInterface) fragment;
//contactsInterface.notifyGranted();
//contactsInterface.notifyDenied();
You can get all the active fragments in the FragmentManager and filter for the ones that have the required interface:
supportFragmentManager.fragments
.map { it as? ContactsInterface }
.filterNotNull()
.forEach {
it.notifyDenied()
}
or if you need to use Java:
List<Fragment> fragments = getSupportFragmentManager().getFragments();
for(fragment : fragments) {
if(fragment instanceof ContactsInterface) {
((ContactsInterface)fragment).notifyDenied();
}
}
You may want to also fail (hard or soft) if no fragments are found.
The advantage of doing this is that you don't care how the fragment was started (manually or by layout) and also don't have to care about reconnecting when the activity restarts for example.
This library may be useful to you.
You should do something like this in your fragment:
#Override
public void onCreateView() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onDestroyView() {
super.onStop();
EventBus.getDefault().unregister(this);
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
MessageEvent is just an example, you can use whatever structure you want.
in the Activity you post messages like this:
EventBus.getDefault().post(new MessageEvent());
Check the documentation of the library to have a better idea and use a better approach to your case.
you can use onCreate() and onDestroy() instead. That depends on your logic and you you are updating the view of the fragment when you receive new messages from the Activity.
Happy coding :).

When Activity is destroyed on background,how to remove fragment

When my app switches to background,the system memory is low, then android will destroy my activity.At that time,I want to remove the fragments attached to activity,so that when activity switches to foreground,I can avoid any abnormal behavior of that activity.I do it like this:
#Override
public void onSaveInstanceState(Bundle outState) {
FragmentManager fm = getSupportFragmentManager();
AbstractBaseViewFragment previous = (AbstractBaseViewFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (previous != null) {
fm.beginTransaction().remove(previous).commitAllowingStateLoss();
fm.executePendingTransactions();
}
super.onSaveInstanceState(outState);
}
so I want to ask:Are there any other good ideas to finish this target?Will my method cause any crashes?
You can try this: ft.detach(previous);
if (previous != null) {
FragmentTransaction ft = fm.beginTransaction();
ft.detach(previous);
ft.remove(previous);
ft.commitAllowingStateLoss();
... // the rest of your code
}

Why addToBackStack() method doesn't work

Here is my main activity. I followed this guide about Fragments correctly. When I click "Back" button, my application is closed instead of returning to the MainScreenFragment. Why is this happening and why addToBackStack() doesn't work?
public class MainScreenActivity extends ActionBarActivity implements MainScreenFragment.OnFrameChoiced {
private MainScreenFragment mainScreenFragment;
private AddWordsFragment addWordsFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_screen);
mainScreenFragment = new MainScreenFragment();
addWordsFragment = new AddWordsFragment();
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.container, mainScreenFragment).addToBackStack(null).commit();
}
#Override
public void choiceFrame(int id) {
switch (id) {
case R.id.add_new_words_frame:
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.container, addWordsFragment).addToBackStack(null).commit();
fm.executePendingTransactions();
break;
}
}
P.S. I tried to use a solution from this topic, but It still doesn't work.
did you try overriding the back like below:
#overide
public void onBackPressed(){
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 1) {
fm.popBackStack();
} else {
finish();
}
}
(I know you have picked up your desirable answer, but I have found a little more against this problem)
Though as Android official site has documented:
By calling addToBackStack(), the replace transaction is saved to the back stack so the user can reverse the transaction and bring back the previous fragment by pressing the Backbutton.
But as a matter of fact, this is in a precondition that you are using the standard android activity, specifically, the android.app.Activity. Because this methods in android.app.Activity will work when Backbutton is pressed:
public void onBackPressed() {
if (mActionBar != null && mActionBar.collapseActionView()) {
return;
}
if (!mFragments.getFragmentManager().popBackStackImmediate()) {
finishAfterTransition();
}
}
But, if you are extending your xxxxActivity from someone else, for example, the AppCompatActivity, FragmentActivity, ActionBarActivity, it will be another story, because in FragmentActivity, onBackPressed() method is totally overrode:
public void onBackPressed() {
if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
supportFinishAfterTransition();
}
}
Note that mFragments.getFragmentManager() is replaced by mFragments.getSupportFragmentManager(), so in cases like this, you should begin your FragmentTransaction using getSupportFragmentManager() of the Activity. and as a consequence of that, you don't have to override onBackPressed method in your Activity.
BTW, ActionBarActivity extends AppCompatActivity extends FragmentActivity,they all come from the support library, you know what I mean, remember to use getSupportFragmentManager() instead of getFragmentManager() when using support library in order to get the compatible behavior.

how to wait until a fragment is removed

I have an activity with dynamic fragments in it. I need to run some code after a fragment is removed but remove(myFragment).commit() is executed asynchronously and i cant know when exactly the fragment is removed.Here is my code:
final FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.remove(myFragment).commit();
//wait until the fragment is removed and then execute rest of my code
From the documentation:
public abstract int commit ()
Schedules a commit of this transaction. The commit does not happen
immediately; it will be scheduled as work on the main thread to be
done the next time that thread is ready.
What if you use the fragment's onDetach method to call the activity and tell it its done?
class MyFrag extends Fragment {
private Activity act;
#Override
public void onAttach(Activity activity) {
act = activity;
}
#Override
public void onDetatch() {
act.fragGone();
}
}
And in the activity:
public void fragGone() {
//do something, update a boolean, refresh view, etc.
}
You could try using the onDetached() callback of the fragment. This will be called whenever it is removed from its Activity.
Use onAttach() to check when the fragment is attached to the Activity and use onDettach() to check when the fragment is dettached to the activity.
Using the onDettach() you can also check to update or not views, data, etc in this way:
#Override
public void onDetach() {
super.onDetach();
synchronized (mLock) {
mReady = false;
}
}

java.lang.IllegalStateException: Activity has been destroyed

Working with Robolectric , I'm very new to android. I made a first test class using Activity. It worked nicely.
Now I want make a test for fragment.
#RunWith(RobolectricTestRunner.class)
public class LoginFragmentTest {
private LoginFragment fragment;
#Before
public void setup() {
fragment = new LoginFragment();
startFragment(fragment);
assertThat(fragment, notNullValue());
assertThat(fragment.getActivity(), notNullValue());
}
private void startFragment(LoginFragment fragment) {
FragmentManager fragmentManager = new FragmentActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(fragment, null);
fragmentTransaction.commit();
}
#Test
public void login() {
EditText idEditText = (EditText)fragment.getActivity().findViewById(R.id.main_id);
assertThat(idEditText, notNullValue());
}
}
This is my first test class for Fragment class. It throws
"java.lang.IllegalStateException: Activity has been destroyed" in startFragment#fragmentTransaction.commit().
Anyone knows how to fix this ?
You can find whole source from https://github.com/msbaek/frame-test
Thanks in advance !!
In my case, specifically, my problem was when creating the activity.
I was using
activity = Robolectric.buildActivity(MyActivity.class).get();
And it should be
activity = Robolectric.buildActivity(MyActivity.class).create().get();
Hope it helps someone :D
#RunWith(RobolectricTestRunner.class)
public class LoginFragmentTest {
private LoginFragment fragment;
#Before
public void setup() {
fragment = new LoginFragment();
startFragment();
assertThat(fragment, notNullValue());
assertThat(fragment.getActivity(), notNullValue());
}
private void startFragment() {
FragmentActivity activity = new FragmentActivity();
shadowOf(activity).callOnCreate(null);
shadowOf(activity).callOnStart();
shadowOf(activity).callOnResume();
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(fragment, null);
fragmentTransaction.commit();
}
#Test
public void login() {
EditText idEditText = (EditText) fragment.getView().findViewById(R.id.main_id);
assertThat(idEditText, notNullValue());
}
}
This is working version. Following 3 lines are important(it's from robolectric source - DialogFragmentTest).
shadowOf(activity).callOnCreate(null);
shadowOf(activity).callOnStart();
shadowOf(activity).callOnResume();
The fragments are supposed to be displayed from an Activity. The flow should be:
allocate a new fragment object in a FragmentActivity class
get the fragment manager to add the newly allocated fragment
In your case you do not have a connection to a real activity. You allocate a FragmentActivity with new FragmentActivity() and try to get the support manager. While this compiles there is no "real" activity able to manage your fragment. Fragments can be added on activities already displayed and here it's not the case.
I recommend reading this page as it explains these things very well: http://developer.android.com/guide/components/fragments.html
That happened for me when I used fragmentTransaction.commitAllowingStateLoss(); from sub Fragment whose parent fragment had setRetainInstance(true); I had activity as property what lead to leaking activity on rotation.

Categories

Resources