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
}
Related
I'm working with an Android app which I inherited. On a certain screen, which is defined as a Fragment, I need to open an external web page, and then wait for that web page to do a redirect back to my app using a custom scheme.
I understand how to open the web page, and I understand how to set up an intent-filter in my Manifest that responds to the custom scheme by starting another activity. But, starting another activity is not what I need. Instead, I need for control to return to the Fragment that originally started this process.
What is the best way to accomplish this?
You have to parse the intent in the activity and use the fragment manager to populate late the fragment you wish. Replace Action and Fragment with your own.
#Override
protected void onNewIntent(final Intent intent) {
super.onNewIntent(intent);
parseIntent(intent);
}
private void parseIntent(Intent intent) {
final String action = intent.getAction();
if (action != null) {
if (Action.<ONE>.equals(action)) {
FragmentManager fm = getFragmentManager();
Fragment<ONE> fragment = (Fragment<ONE>) Fragment.instantiate(this,
Fragment<ONE>.class.getCanonicalName(),
getIntent().getExtras());
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.fragment_id, fragment);
ft.commit();
} else if (Action.<TWO>.equals(action)) {
FragmentManager fm = getFragmentManager();
Fragment<TWO> fragment = (Fragment<TWO>) Fragment.instantiate(this,
Fragment<TWO>.class.getCanonicalName(),
getIntent().getExtras());
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.fragment_id, fragment);
ft.commit();
}
}
}
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!
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.
I have main activity which embeds fragment:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vd = VirtualDatabaseTableProvider.getInstance(getApplicationContext());
fm = getSupportFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
//Create Layout and add fragments
setContentView(R.layout.main_window);
ListFragment ListFragment= new ListFragment();
android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_pane, ListFragment, "List");
//ft.replace(R.id.fragment_pane, ListFragment);
ft.addToBackStack(null);
ft.commit();
//Initialising buttons
imgBtnFontInc = (ImageButton) findViewById(R.id.ImgBtnUpFont);
imgBtnFontInc.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(textViewAttached){
try{
//Some text Resize
}
}catch (NullPointerException npe){
Log.e(TAG, "Error on calling Text resize");
Log.e(TAG, npe.getMessage());
Log.e(TAG, npe.getStackTrace().toString());
}
}
}
}
);
/* More Buttons code..... */
imgBtnFontDec.setVisibility(View.GONE);
imgBtnFontInc.setVisibility(View.GONE);
/* Some Saved State handling to recover detailed text Screen*/
if(savedInstanceState != null){
if (savedInstanceState.containsKey("UUID")){
try{
String uuid = savedInstanceState.getString("UUID");
if (uuid != null){
iniTextScreen(uuid);
}
}catch (Exception e){
Log.e(TAG, "Unable To return text");
}
}
}
Text is initialised with function:
private void initTextScreen(String StringID){
Bundle Data = new Bundle();
Data.putString("UUID", StringID);
TextScreenFragment TextFragment = new TextScreenFragment();
TextFragment.setArg1ments(Data);
if(fm == null){
fm = getSupportFragmentManager();
}
android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations( R.anim.animation_enter, R.anim.animation_exit);
ft.replace(R.id.fragment_pane, TextFragment, "TextFragment");
ft.addToBackStack(null);
ft.commit();
}
I handled Buttons visibility in main activity with simple Callback from TextScreenFragment. Callback in main activity:
public void onTextViewAttached() {
textViewAttached = true;
MainActivity.this.imgBtnFontDec.setVisibility(View.VISIBLE);
MainActivity.this.imgBtnFontInc.setVisibility(View.VISIBLE);
}
Callback called in TextScreenFragment:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof Callbacks)) {
throw new IllegalStateException(
"Activity must implement fragment's callbacks.");
} else {
listener = (Callbacks) activity;
listener.onTextViewAttached();
}
}
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
public void onTextViewAttached();
}
It works, however when I put android phone is switch potrait/landscape mode: onAttached in fragment get called way before onCreate in main Activity and Button objects.
How can fragment be attached on main activity before even onCreate is called in main activity?
I attached a particular fragment at very end of onCreate method after buttons were already initialized,but why onAttach in fragment is called before even I attach fragment and get null exception, because button objects were not initialized in onCreate? How it is even possible?
When I comment out:
`// MainActivity.this.imgBtnFontDec.setVisibility(View.VISIBLE);
//MainActivity.this.imgBtnFontInc.setVisibility(View.VISIBLE);`
in callback function public void onTextViewAttached(), no more crashes, but still I noticed that onAttach is called before main activity created and is called twice:
one time with uninitialised activity from hell knows were (every element of main activity is either null or has default values), second time when fragment is properly attached from main activity onCreate.
I made conclusion that on orientation switch fragments gets atached to uninitialised activity. Am I missing something, on orientation change should I call some other function before onCreate in main activity to get buttons from layout?
Is it some kind of fragment automated attach behaviour which I am not aware of and I could take advantage of?
What is life cycle of activity with fragment attached to it, because onAttach called in fragment before even main activity is created seems counter intuitive.
I'm developing an app that basically has an ActionBar. When my app starts, the Activity creates the fragments and attaches them to each tab, so when I switch I get different views.
The problems arise when I try to rotate the device. After some struggle, I noticed that Android automatically recreates the previously added fragments like this:
SummaryFragment.onCreate(Bundle) line: 79
FragmentManagerImpl.moveToState(Fragment, int, int, int) line: 795
FragmentManagerImpl.moveToState(int, int, int, boolean) line: 1032
FragmentManagerImpl.moveToState(int, boolean) line: 1014
FragmentManagerImpl.dispatchCreate() line: 1761
DashboardActivity(Activity).onCreate(Bundle) line: 864
...
and then I recreate the fragments as usual. So I have the "real" fragments that I expect to work correctly and their "hidden" Android-created counterparts that make my app crash. How can I avoid this behavior? I already tried to call setRetainInstance(false) in the SummaryFragment.
Thank you
You need to check for a savedInstanceState [edit: in your parent activity], and if it exists, don't create your fragments.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if (savedInstanceState == null) {
// Do your oncreate stuff because there is no bundle
}
// Do stuff that needs to be done even if there is a saved instance, or do nothing
}
When you create your activity, check to make sure that it doesn't already exist. If it exists, do nothing...Android will recreate it for you.
private void initFragment() {
FragmentManager fragMgr = getSupportFragmentManager();
if (fragMgr.findFragmentByTag(LEADERBOARD_FRAG_TAG) != null) { return; }
frag = new HdrLeaderboardFragment();
FragmentTransaction ft = fragMgr.beginTransaction();
ft.replace(R.id.leaderboard_fragment_wrapper, frag, LEADERBOARD_FRAG_TAG);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.commit();
}
If you have the similar ui(no specific layout-land files) for both orientations you can set android:configChanges="keyboardHidden|orientation" to the activity in your manifest file.
If don't provide please the source code where you're adding the fragments to tabs, and I'll try to help you with improvements.
I am not sure if there is a better solution, but this is what i did in latest program.
When a fragment is created automatically by system on orientation change and if you want to keep track of them in host activity, catch them in host activity's OnAttachFragment() method. And they get the arguments by default, so you can use them to find out which fragment it is.
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment != null) {
if(fragment.getArguments() != null) {
switch (fragment.getArguments().getString(ARG_PARAM1)) {
case FragmentATag:
if (myFragmentA != fragment) {
myFragmentA = (FragmentA) fragment;
}
break;
case FragmentBTag:
if (myFragmentB != fragment) {
myFragmentB = (FragmentB) fragment;
}
break;
}
}
}
}