View was clear data when screen rotation in Android - android

I have a View that was created on runtime then I draw some canvas on that View(runtime) after that I rotated my screen.All data was gone(reset).So I put the some code in AndroidManifest.xml like this
android:configChanges="keyboardHidden|orientation"
in my <activity> then I put a #Override function
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setContentView(R.layout.main);
LinearLayout layout = (LinearLayout) findViewById(R.id.myPaint);
layout.addView(mView);
}
but everything couldn't solved my problem.I want to keep my data from View(runtime) on every single rotation.
That's my onCreate function.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mView = new MyView(this);
setContentView(mView);
mView.requestFocus();
setContentView(R.layout.main);
LinearLayout layout = (LinearLayout) findViewById(R.id.myPaint);
layout.addView(mView);
}

You need to save and load the data you want to retain. Even though you're handling the screen rotation yourself when you modified the Manifest the way you did, you're still reloading the view yourself. Reread the reference document on Handling Runtime Changes. You need to store your data and reload it accordingly. Otherwise it will be lost when the application restarts or when you reload your ContentView.
http://developer.android.com/guide/topics/resources/runtime-changes.html

You could approach this a few ways.
I assume MyView is your own class which extends View. If so there are two methods which you may care to know, onSaveInstanceState() and onRestoreInstanceState(). When saving you create a parcelable that will contain enough data for you to re-render your view if it were to be destroyed and recreated.
class MyView extends View {
private String mString;
onDraw(Canvas v) { ... }
Parcelable onSaveInstanceState() {
Bundle b = new Bundle();
bundle.putString("STRING", mString);
return b;
void onRestoreInstanceState(Parcelable c) {
Bundle b = (Bundle) c;
mString = bundle.getString("STRING", null);
}
}
Activity has similar state saving mechanics allowed in onCreate and onSaveInstanceState() (inside Activity, not View in this case) which will allow the activity to reset the state of it's view to the state it desires.
This should solve most of your worries. If you are wanting to use the onConfigurationChanged method, then you should reclarify your question as it is not clear what the current behavior is that you aren't expecting in each situation (only using onConfigurationChanged, or only using onCreate, or using both, etc).

I've just used my data-class as singleton (java-pattern).
And it works fine.
--> Application is a Stop-Timer for Racing, where i can stop time from different opponents on the track, so i need the data for longer time, also if the view is repainted.
regz
public class Drivers {
// this is my singleton data-class for timing
private static Drivers instance = null;
public static Drivers getInstance() {
if (instance == null) {
instance = new Drivers();
}
return instance;
}
// some Timer-Definitions.......
}
Then in MainActivity:
// now the class is static, and will alive during application is running
private Drivers drivers = Drivers.getInstance();
#Override
public void onClick(View v) {
if (v == runButton1) {
drivers.startTimer1();
// do some other crazy stuff ...
}
}
// here i put out the current timing every second
private myUpdateFunction(){
time01.setText(drivers.getTimer1());
// update other timers etc ...
}

Related

Keeping TextView visibility (View.INVISIBLE) and Button state (setEnabled(false)) after screen rotation

I have an app with a Button to show the answer of a question asked. And a TextView with a warning text and another empty one that displays the answer when the button is clicked. When the user clicks the button, I want the warning textView to disappear and the button to be "unclickable". I managed to achieve this and everything worked as intended, but the problem occurs when I rotate the screen, nothing stays the same.
The code below is in onCreate().
Button buShowAnswer = findViewById(R.id.buShowAnswer);
TextView tvShownAnswer = findViewById(R.id.tvShownAnswer);
TextView tvWarning = findViewById(R.id.tvWarning);
buShowAnswer.setOnClickListener((View v) -> {
String answer;
if (isAnswerTrue){
answer = getString(R.string.true_answer);
}else {
answer = getString(R.string.false_answer);
}
tvWarning.setVisibility(View.INVISIBLE);
buShowAnswer.setEnabled(false);
tvShownAnswer.setText(answer);
cheatState = true;
});
It is very simple. You can go with navylover or you do the following
Open your manifest file
add android:configChanges="keyboardHidden|orientation|screenSize" to your activity like
<activity android:name=".MainActivity"
android:configChanges = "keyboard|orientation|screenSize"
//and leave the rest of the code of your activity as it is. Just add this line and you are good to go
What Happen when you rotate your screen? actually your activity configuration changes and you may say your activity is created as new again. When you rotate your screen it actually destroy your activity and recreate it and everything reset back.
But this is not a best option to use this line of code. you can use viewModal. it saves your avtivity state and when your activity destroyed and recreated then the same viewModal Linked with that activity which helps in saving configuration and saving states
Hope this is Helping
You should use onSaveInstanceState to save the state of your local variable when the screen is rotated, then use the saved value in onCreate (which is called upon rotation to create the new rotated activity).
So add this function
private boolean cheatState = false;
private static final String CHEAT_STATE = "CHEAT_STATE";
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putBoolean(CHEAT_STATE, cheatState);
// call superclass to save any view hierarchy
super.onSaveInstanceState(outState);
}
and in your onCreate you can then call
if (savedInstanceState != null) {
cheatState = savedInstanceState.getBoolean(CHEAT_STATE);
if( cheatState ) {
buShowAnswer.setEnabled(false);
tvWarning.setVisibility(View.INVISIBLE);
}
}
Oncreate() callback will be triggered when screen rotates, add below line in Androidmanifest.xml to prevent recreate activity.
android:configChanges="keyboardHidden|orientation|screenSize
The detailed explantion is here
My answer is similar to Tyler's above, but it saves and restores both visibility and enabled states.
private boolean isAnswerEnabled;
private int warningTextVisibility;
private static final String STATE_ANSWER = "state_cheat";
private static final String STATE_WARNING_VISIBILITY = "state_warning_visibility"
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(STATE_ANSWER, isAnswerEnabled);
oustState.putInt(STATE_WARNING_VISIBILITY, warningTextVisibility);
}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
isAnswerEnabled = savedInstanceState.getBoolean(STATE_ANSWER);
buShowAnswer.setEnabled(isAnswerEnabled);
warningTextVisibility = savedInstanceState.getInt(STATE_WARNING_VISIBILITY);
tvWarning.setVisibility(warningTextVisibility);
}
}

Android Destroy current view from parent

I have a Parent activity that sets a view on Resume based on some check like this :
public class AppLockActivity extends AppCompatActivity {
#BindView(R.id.btnSubmit)
Button submitButton;
private static final String TAG = "AppLockActivity";
private static TimeElapsed timeElapsedInstance;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
timeElapsedInstance = TimeElapsed.getInstance();
timeElapsedInstance.resetTime();
timeElapsedInstance.setStartTime();
}
#Override
protected void onResume() {
super.onResume();
//check if app has passed a time threshold
if(timeElapsedInstance.getStartTime() != 0){
timeElapsedInstance.setEndTime(Calendar.getInstance().getTimeInMillis());
long threshold = timeElapsedInstance.getEndTime()-timeElapsedInstance.getStartTime();
Log.d(TAG,"Threshold : "+threshold);
//Current timeout threshold set to 30s
if(threshold>30000){
setContentView(R.layout.activity_app_lock);
ButterKnife.bind(this);
}else{
}
}
}
#OnClick(R.id.btnSubmit) void onSubmit() {
//destroy current(Parent) view and show the previous
}
}
This activity is extended by other activities like MainAcitivty ,etc...
public class MainActivity extends AppLockActivity{
#Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
}
}
When the app goes in background and is resumed the onResume function is called and based on the check the new View is set - R.layout.activity_app_lock. What I want to do is onClick of the submit button in this view I want to destroy the current view i.e (R.layout.activity_app_lock) and show the previous view that was in the child activity like MainActivity (R.layout.activiyt_main)...
Anybody have any idea how can I do this?
Thanks
You can actually call setContentView again with a different view. All your bindings need to be reset and your On_____Listeners need to be cleared or else you'll get a memory leak. Other than that, it'll be up and ready for you to go.
Though I suggest an alternative approach to changing the layout. Instead, create a new Activity that you start in replacement of the layout your currently submitting. Then, rather than worrying about leaks, you just call finish() on the lock Activity when the user submits. The effect would be the same and it would be more versatile (In my opinion).

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.

setRetainInstance(true) does not persist member variables of the Fragment

I am using a View-less Fragment to store some data during orientation change of my Activity. It looks roughly like this:
public class BoardActivity extends BaseActivity {
private DataHandler mDataHandler;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// initialize the data handler
mDataHandler = (DataHandler)mFragmentManager.findFragmentByTag("data");
if (mDataHandler == null) {
mDataHandler = new DataHandler();
mFragmentManager.beginTransaction().add(mDataHandler, "data").commit();
// initialize the data
mDataHandler.mThreads = ...;
} else {
// here, the data is taken and the ListView is filled again.
fillView();
}
}
public static class DataHandler extends Fragment {
private Topic[] mThreads;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
}
}
What happens is, that when the Activity is left (vor example with the home button) and for some reason is killed in the background, the app crashes upon restart of that Activity. The reason is that although the Fragment mDataHandler is found by the FragmentManager, its Member variable (mThreads) is null.
How come the Fragment itself can be retained but its variables are set to zero?
How come the Fragment itself can be retained but its variables are set to zero?
The fragment was not retained. Retained fragments are retained only for configuration changes. You did not go through a configuration change. Your process was terminated, because Android needed the RAM to support other apps.

Simple persisting doesn't work

private static final String KEY = "qaz";
private String aString;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.txt);
final String saved;
if (savedInstanceState != null) {
saved = savedInstanceState.getString(KEY);
report("[" + saved + "]");
} else {
saved = null;
report("[NULL]");
}
if (aString==null && saved!=null) {
aString = saved;
report("A");
}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
report("s");
savedInstanceState.putString(KEY, aString);
super.onSaveInstanceState(savedInstanceState);
}
#Override
protected void onPause() {
report("p");
super.onPause();
}
#Override
public void onBackPressed() {
report("b");
super.onBackPressed();
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
private void report(String s) {
aString += " " + s;
Log.v("report", aString);
textView.setText(aString);
}
onSaveInstance is intended to persist a value from one instance of your activity to the next, for example when the activity is destroyed and recreated on an orientation change. Just remember that when your activity is recreated, it is an entirely new instance of the class you've defined to extend the Activity class and therefore, all local fields (variables) will be re-initialised.
As you've already discovered, SharedPreferences are a good way to persist a value beyond the Activity lifecycle and of course, there are others. The values saved in onSaveInstance and retrieved in onCreate are intended for you to be able to initialise a new instance of your activity to the same state it was before it was destroyed.
Imagine for example that you have a messaging application and your user has already entered several lines of text. They then accidentally flip the orientation (we've all done that right?) then flip it back. How frustrated would they be to see that their text had disappeared?! So a good developer would save the current content of the TextView in onSaveInstance and retrieve it in onCreate. If any value is retrieved, it is passed to the setText() method so that the user can continue from where they were.
Take a look here. Learning the Activity life cycle, how to control it and how to pass values between Activities (either 2 different Activities or 2 instances of the same Activity) is one of the keys to unlocking the Android kingdom.
http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle
The key point is to understand when each callback is called and, even more importantly, what may or may not be called by Android. For example, when your app is in the background,there are NO guarantees that your activity will be called again if Android decides to kill your app.
Good luck.

Categories

Resources