Android fragment re-use during re-orientation of screen - java

I'm struggling a bit with my use of fragments for ICS. I've got a fragment (call it AnalysisFragment) that does a fair amount of number-crunching work, reading in a file and then performing a number of calculations on it. Most of this work is initiated in onActivityCreated, because I need to consult the app's settings file prior to doing the work, which in turn requires having the context, which is accessible through the activity, which isn't necessarily available until onActivityCreated is invoked.
Well, this work seems to be getting done again (twice!) whenever I rotate the display. In my LogCat, I see onDestroyView and onDestroy happening (once), followed by onCreate, onCreateView, and onActivityCreated (twice), all pertaining to AnalysisFragment.
Obviously, the work of re-painting the screen for a different orientation has to be done over again, but is there any way I can add avoid the number-crunching work a 2nd time (not to mention a 3rd time)?
I maybe should add that I'm instantiating a new copy of AnalysisFragment whenever the user selects a new file to analyze, as follows
public static AnalysisFragment newInstance
(
String ndsFileName,
ViewIndicator viewIndicator
)
{
AnalysisFragment analysisFragment = new AnalysisFragment( );
Bundle bundle = new Bundle( );
bundle.putString( "nds_file_name", ndsFileName );
bundle.putString( "view_indicator", viewIndicator.toString( ) );
analysisFragment.setArguments( bundle );
return analysisFragment;
}
However, during a re-orientation, Android must be doing it for me. After shutting down the instance I created, it instantiates another using the same bundle for the new screen orientation. Eventually, onActivityCreated gets called on the new instance, and my code is off reading in the same file it read moments ago and re-doing the number crunching.
Any insight would be appreciated ...

Related

Saving state of UI in Android

I'm making a to-do list app and after user presses the button I create a new GridLayout(and all the data about time and name of the task inside of it) and add it into my RelativeLayout. How do I save those GridLayouts in UI so after the activity is destroyed and launched again those layouts are there.
After pressing the button I trigger the Create Activity method
public void CreateActivity(String name,int hours, int minutes,int i)
{
RelativeLayout.LayoutParams relparams= new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,RelativeLayout.LayoutParams.WRAP_CONTENT);
relparams.addRule(RelativeLayout.BELOW,i);
relparams.setMargins(0,50,0,100);
Glayouts.add(new GridLayout(this));
Glayouts.get(i+1).setBackgroundColor(Color.GRAY);
Glayouts.get(i+1).setMinimumWidth(relative.getWidth());
Glayouts.get(i+1).setId(i+1);
Glayouts.get(i+1).setPadding(10,0,0,0);
GridLayout.LayoutParams namee = new GridLayout.LayoutParams();
namee.columnSpec = GridLayout.spec(0);
namee.rowSpec = GridLayout.spec(0);
namee.setGravity(Gravity.LEFT);
final TextView Actname = new TextView(this);
Actname.setText(name);
GridLayout.LayoutParams checkbox = new GridLayout.LayoutParams();
checkbox.columnSpec = GridLayout.spec(1);
checkbox.rowSpec = GridLayout.spec(0);
checkbox.setGravity(Gravity.RIGHT);
CheckBox check = new CheckBox(this);
// ADDING TO LAYOUT
Glayouts.get(i+1).addView(Actname,namee);
Glayouts.get(i+1).addView(check,checkbox);
relative.addView(Glayouts.get(i+1),relparams);
Theoretically when you extends View, then you can also override onSaveInstanceState and onRestoreInstanceState methods, where you must provide your own SavedState class that typically extends BaseSavedState. You can find info on that here
In your case, your layout is dynamic, therefore this doesn't really work. To tell you the truth, your layout probably shouldn't be constructed this way, you should be rendering the grid using a RecyclerView based on a "model" that describes this layout, render the items of the grid via the RecyclerView.Adapter, and you should persist either the "model", or the data you use to construct this model along with the user-inputted state so that you can re-construct the model that will be rendered via your RecyclerView.
You can read more about RecyclerView here.
You can read more about data persistence here.
You can read about using onSaveInstanceState to save data in Activities/Fragments across config change and process death (but not finishing then restarting the app) here.
You can’t. The best way to save state is to use some persistence mechanism, for example database (I’d recommend Room as it is officially supported by Google).
After clicking a button, you should put all the needed information (name, hours, minutes) in the database and when Activity is created, you can read all persisted data and - basing on it - create all needed layouts again.
Another option is storing data in SharedPreferences - it is much easier to setup, so you can also start with this solution. Please note, I'm suggesting it as a first step in the world of persistency in Android, not as a preferred solution for storing data.

Application or activity takes time to load some times

I have created a startup activity from where I am calling another activity which has a view pager and shows some introductory pages.
This app was taking time to load so I thought to display a progress dialog till the activity loads, but that progress dialog also appears few seconds later.
startup activity:
public class StartUpActivity extends AppCompatActivity {
boolean isUserFirstTime, login;
public static String PREF_USER_FIRST_TIME;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
isUserFirstTime = Boolean.valueOf(Utils.readSharedSetting(StartUpActivity.this, PREF_USER_FIRST_TIME, "true"));
Intent introIntent = new Intent(StartUpActivity.this, SlidingActivity.class);
introIntent.putExtra(PREF_USER_FIRST_TIME, isUserFirstTime);
ProgressDialog dialog = new ProgressDialog(StartUpActivity.this);
dialog.setMessage("Welcome to Mea Vita, please wait till the app loads.");
dialog.setCancelable(false);
dialog.setInverseBackgroundForced(false);
dialog.show();
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
//Here you can send the extras.
startActivity(new Intent(StartUpActivity.this,SlidingActivity.class));
// close this activity
finish();
}
}, 4000);
}
}
This doesn't happen every time,only sometimes. What can be the possible reason for this? how can I stop this?
Any solution? Thank you..
There is a strange issue with newly released Android Studio 2.0 (same issue in 2.1) first time of launching application take longer than usual (e.g. 2, 3 seconds or sometimes screen blinks or goes black) this issue happens only in debug mode and not effect your released APK.
A temporary solution to fix this is disabling instant run:
Settings → Build, Execution, Deployment → Instant Run and uncheck Enable Instant Run
First of all, make as rule to make all data loading in async tasks, you must check activity that you want to start where you load data.
The problem is in your second activity.
oncreate method should be used only to make findviews or start async tasks, don't load any in oncreate or in onstart or in onresume.
Probably you are loading high res images in sliding layout or you loading data in it.
There is another way, load all data in async task on first activity, then with ready data start second activity with already data loaded.
There are a few things that can load slowly.
Android need to read your code from storage and load the classes into ram.
I assume Utils.readSharedSetting(StartUpActivity.this, PREF_USER_FIRST_TIME, "true") reads from preferences. That's a file that you're reading from synchronously.
Actually launching the dialog takes a very small amount of time.
I'd suggest showing your loading inside the activity itself to minimize the work needed to render it.
Also, you can store PREF_USER_FIRST_TIME as a boolean instead of a String.

How can I prevent "java.lang.IllegalStateException: Fragment already added" when replacing fragments?

Despite my efforts to prevent fragments from being added more than once, I continue to encounter java.lang.IllegalStateException: Fragment already added: VideoFragment.
I have an activity where VideoFragment is instantiated in onCreate only. In the only place I attempt to display the VideoFragment, I first check whether this fragment has been added already.
private VideoFragment videoFragment;
public void onCreate(Bundle savedInstanceState) {
...
videoFragment = new VideoFragment();
...
}
private void showVideoFragment() {
if (!videoFragment.isAdded()) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, videoFragment, "video").commit();
}
}
I have not been able to consistently reproduce this problem to examine in the debugger, but my runtime error reporting continues to report the exception java.lang.IllegalStateException: Fragment already added: VideoFragment for users, with stack traces composed of Android classes.
/FragmentManager.java:1133→ android.app.FragmentManagerImpl.addFragment
/BackStackRecord.java:648→ android.app.BackStackRecord.run
/FragmentManager.java:1453→ android.app.FragmentManagerImpl.execPendingActions
/FragmentManager.java:443→ android.app.FragmentManagerImpl$1.run
/Handler.java:733→ android.os.Handler.handleCallback
/Handler.java:95→ android.os.Handler.dispatchMessage
/Looper.java:146→ android.os.Looper.loop
/ActivityThread.java:5487→ android.app.ActivityThread.main
/Method.java:-2→ java.lang.reflect.Method.invokeNative
/Method.java:515→ java.lang.reflect.Method.invoke
/ZygoteInit.java:1283→ com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run
/ZygoteInit.java:1099→ com.android.internal.os.ZygoteInit.main
/NativeStart.java:-2→ dalvik.system.NativeStart.main
Does the definition of added in isAdded() not match the one used to check fragment transactions?
Or is there some way the videoFragment reference in the activity is not the same? Is this something I need to explicitly handle when saving state http://developer.android.com/guide/components/activities.html#SavingActivityState?
Or is there a reliable alternative way of checking whether the fragment has already been added?
UPDATE
I have figured out how to semi-reliably produce the problem.
Start application
Navigate away from application, and run other programs for awhile. On my Galaxy Nexus (which is pretty slow these days), using Chrome to read a few news articles seems sufficient. When returning to the home screen, if it takes a few seconds to render then the application will likely throw the fragment exception.
Restart application and trigger fragment change
If I kill and simply run the application, everything appears to be fine. Or if I navigate away from the application and come back immediately, it works okay. It's only if the application is left in the background for a bit (enough to remove from memory?), that the fragment issue appears.
I also tried, to no effect, in onCreate
View v = findViewById(R.id.fragment_container);
if(v != null){
Log.d(TAG, "disabling save for fragment_container");
v.setSaveEnabled(false);
v.setSaveFromParentEnabled(false);
}
I also tried checking Fragment prior = getFragmentManager().findFragmentByTag("video"); and Fragment prior2 = getFragmentManager().findFragmentById(R.id.fragment_container); before running the replace fragment transaction, but these come up null.
My problem in fact looks very similar to
https://code.google.com/p/android/issues/detail?id=61247
though the time appears less an issue than memory/cache effects. It is completely unclear to me why that issue was closed.
I will try to produce a simple application that replicates this issue. My current one uses webrtc, and the logcat output is completely cluttered with webrtc messages.
I see a few things here:
Your problem occours propably when Activity is recreated by the system. You can simply simulate it by changing device orientation.
isAdded() returns false, because Activity was recreated so this method is called for new instatnce of VideoFragment which hasn't knowledge about previous add.
showVideoFragment() actually add fragment to Activity instead of just show it. I suggest you rename that method to somethink like "addVideoFragment" and move it to onCreate() method. If you do that you resolve the issue.
If you really want to show or hide fragment use methods from FragmentTransaction eg:
FragmentManager fm = getFragmentManager();
fm.beginTransaction()
.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out)
.show(somefrag) // or hide
.commit();
Hint:
When you a priori know that you fragment is always the VideoFragment you can simply use:
<fragment
android:name="com.example.VideoFragment"
android:id="#+id/video_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
find it:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_layout);
VideoFragment fragment = (VideoFragment) getFragmentManager().findFragmentById(R.id.video_fragmen);
}
and make whatever you want with the instance.
I think I have successfully fixed this error, by trying to reproduce this in a simpler example:
https://stackoverflow.com/a/30672516/4107809
I was making a mistake where multiple instances of a fragment (not the VideoFragment) were added in successive calls to onCreate caused by recreation of the Activity. This fragment addition did not trigger the java.lang.IllegalStateException: Fragment already added because apparently this happens only if you try to add the same fragment instance more than once, not multiple instances of the same fragment.
Upon calling the fragment replace method, the java.lang.IllegalStateException: Fragment already added is generated for the new VideoFragment, even though the VideoFragment is only added once using replace.
By ensuring the different fragment was added only once, the replace by the VideoFragment no longer generates java.lang.IllegalStateException: Fragment already added: VideoFragment, at least for the steps for reproducing I outlined above. The IllegalStateException appears to have nothing to do with adding/replacing the VideoFragment, but with the state of the fragments being replaced.
I am displeased by this resolution for two reasons:
The error message is misleading. It says the VideoFragment has already been added, and I have resolved this by making sure that a different fragment is not added more than once, which did not generate an exception.
The replace documentation is very misleading. Based on my reading, it should not matter what the state of the fragment container is prior to calling to replace; the end state should be determined solely by the fragment that is added from the replace argument. I think this discrepancy is most clear in the linked question, though the answerer in that question disagrees.
Replace an existing fragment that was added to a container. This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here.

How to host widgets and how to update them always reliably?

I'm hosting widgets in my app. They can be picked from a list of all installed widgets and are added to a view. I save their IDs to database and restore them from these saved IDs again after restart of the app.
This works most of the times, but not always. Widgets are not always updated properly. One scenario where that happens for sure is:
"rebooting phone and starting app before boot-process of device has finished."
(if i wait until booting is completed, it works!!!)
There must be another scenario, but i couldnt reproduce that. I restart my app and they are not updated. So i wonder if i am missing something important.
I have basically used this tutorial:
http://leonardofischer.com/hosting-android-widgets-my-appwidgethost-tutorial/#comment-14678
My Code:
onCreate:
mAppWidgetManager = AppWidgetManager.getInstance(this);
mAWH_AppWidgetHost = new MyAppWidgetHost(MyApp.getContext(), R.string.APPWIDGET_HOST_ID);
onStart:
mAWH_AppWidgetHost.startListening();
onStop:
mAWH_AppWidgetHost.stopListening();
restore from id (saved in database):
AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(appWidgetId);
MyAppWidgetHostView hostView = (MyAppWidgetHostView) mAWH_AppWidgetHost.createView(this, appWidgetId, appWidgetInfo);
hostView.setAppWidget(appWidgetId, appWidgetInfo);
mLL_innerLayout.addView((MyAppWidgetHostView) hostView);
I cannot find any sites in the web that make this clear. Neither does looking at source code of different launcher apps make it more clear to me.
I had the same problem, but then I moved
mAWH_AppWidgetHost.startListening();
in onCreate method and
mAWH_AppWidgetHost.stopListening();
in onDestroy method. This is suggested here

What is better for my needs, onPause() or onSaveInstanceState()?

i have an app that has three pages, one of them is the main page. the user can enter in a few fields that i would like to save if the user goes to one of the two sub pages. i have been looking into the onPause() and into onSaveInstanceState(). i guess i just want a clear explanation on the two, and if onPause() is better and example of the code. this is what i ahve for onSaveInstanceState().
protected void onSaveInstanceState(Bundle outState) {
// Save away the original text, so we still have it if the activity
// needs to be killed while paused.
outState.putDouble("quizPts",qpts);
outState.putDouble("quizV",qvalue);
outState.putDouble("tPts",tpts);
outState.putDouble("tValue", tvalue);
outState.putDouble("hPts", hpts);
so that is how i am setting up bundle, by giving it an ID and a value.
public void onRestoreInstanceState(Bundle outState) {
super.onRestoreInstanceState(outState);
// Restore UI state from the savedInstanceState.
// This bundle has also been passed to onCreate.
qpts = outState.getDouble("quizPts");
qvalue = outState.getDouble("quizV");
tpts = outState.getDouble("tPts");
tvalue = outState.getDouble("tValue");
hpts = outState.getDouble("hPts");
this is how i plan to retore it, the problem is i dont understand how to pass the Bundle around to restore it. i am setting the variables that i need to back to the variables that get set to the UI.
any advice would be great
thanks from a beginner androider
The best option will be a shared preference. The onpause is designed for attending your concerns when app is paused due to a phone call or something. But if you use shared preference it gives you the method for saving your data and recover it wit default values if the saved value is not available. This data will persist across user sessions (even if your application is killed). But it is not a good option if you are planning to save something other than primitive data types like bool,int etc.
see http://developer.android.com/guide/topics/data/data-storage.html#pref
You don't need to pass the bundle around yourself: the Activity framework takes care of that. Use onSaveInstanceState(): if your Activity class is destroyed for any reason by the system it will be called so you should be fine putting your logic in there. onPause will always be called if you leave your Activity, regardless of if the Activity is destroyed.
I would also add a check in your onRestoreInstanceState:
public void onRestoreInstanceState(Bundle outState) {
super.onRestoreInstanceState(outState);
// Restore UI state from the savedInstanceState.
// This bundle has also been passed to onCreate.
if(outState.containsKey("quizPts")) qpts = outState.getDouble("quizPts");
if(outState.containsKey("quizV")) qvalue = outState.getDouble("quizV");
if(outState.containsKey("tPts")) tpts = outState.getDouble("tPts");
if(outState.containsKey("tValue")) tvalue = outState.getDouble("tValue");
if(outState.containsKey("hPts")) hpts = outState.getDouble("hPts");

Categories

Resources