I have UI displaying details of a place, including some EditTexts and an adapter from this ExpandableRecyclerView library.
The UI is updated in onCreate when my ViewModel observes a change to the place. I save the adapter in onSaveInstanceState and restore it in onRestoreInstanceState.
However once I rotate my device, the old UI state is displayed. For example, I enter some text to the EditText and expand my adapter, but after I rotate my device the old text and collapsed adapter appears. I think this is because adapter is null until onChanged is called, so my adapter method in onRestoreInstanceState is skipped, and EditTexts are re-updated to old text in onChanged.
How do I save the state of my UI?
Full code can be found here.
onCreate:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
viewModel = new ViewModelProvider(this).get(PlaceViewModel.class);
Intent intent = getIntent();
if (intent.hasExtra(EXTRA_PLACE_ID)) {
String id = intent.getStringExtra(EXTRA_PLACE_ID);
viewModel.getPlaceById(id).observe(this, new Observer<PlaceModel>() {
#Override
public void onChanged(PlaceModel placeModel) {
// placeModel will be null if place is deleted
if (placeModel != null) {
// update UI here
}
}
});
}
}
onSaveInstanceState and onRestoreInstanceState:
/* To save the expand and collapse state of the adapter,
you have to explicitly call through to the adapter's
onSaveInstanceState() and onRestoreInstanceState() in the calling Activity */
#Override
protected void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (adapter != null) {
adapter.onSaveInstanceState(outState);
}
}
#Override
protected void onRestoreInstanceState(#NonNull Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (adapter != null) {
adapter.onRestoreInstanceState(savedInstanceState);
}
}
One solution is just check if savedInstanceState is null, if it is don't set text of EditTexts and restore adapter state.
But I didn't carefully read the ViewModel documentation: "The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations."
I was asking why ViewModel is saving my old UI-related data after rotation...even though that's exactly what it's supposed to do...my mistake.
Related
I'm having a lot of trouble with Android Fragments, and I don't know why.
I want to keep an object in memory whenever I pause the app (onPause()).
I have a MapView with some layers. And I want to keep those layers, so the map doesn't have to reload from scratch and recreate them whenever I pause and resume the app.
The thing is, I can't save them with onSaveInstanceState(). They are objects, not primitives. They are also from a library, so I can't make them implement Serializable or Parcelable.
I want to find a solution without resorting to:
android:configChanges="orientation|screenSize">
to my Activity, since it is considered bad practice. Also, it doesn't work for when you pause and resume the app.
I also tried
this.setRetainInstance(true); // retain this fragment
on my fragment's onCreate(), but I would also like to avoid it. It doesn't work anyway.
After much trial, error and testing, I found out that my fragment's onSaveInstanceState() doesn't even run. I can't even keep a simple boolean whenever I pause and resume the app.
I read:
Saving Android Activity state using Save Instance State
https://developer.android.com/training/basics/activity-lifecycle/recreating
savedInstanceState is always null
Amog many other posts and, of course, the Android documentation.
Some of my code:
Creating the fragment:
navFragment = FragNavigation.newInstance(); // keeping the reference to the navFragment
// setting up the viewpager
ViewPager viewPager = findViewById(R.id.vpMainViewPager);
MainPagerAdapter mainAdapter = new MainPagerAdapter(getSupportFragmentManager(), this);
mainAdapter.addFragment(navFragment);
viewPager.setAdapter(mainAdapter);
Trying to save a simple boolean in the instance state (remember: this doesn't even run):
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
Log.e(TAG, "onSaveInstanceState: is map loaded? " + mapIsLoaded );
outState.putBoolean("mapIsLoaded", mapIsLoaded);
}
And trying to load it:
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setRetainInstance(true); // retain this fragment
Log.e(TAG, "onCreate: null? " + (savedInstanceState == null) );
if (savedInstanceState != null)
mapIsLoaded = savedInstanceState.getBoolean("mapIsLoaded", false);
}
What am I missing? I'm clearly not understanding something. Why is it so hard to keep values when you pause the app?
I would suggest storing the MapView layers using a ViewModel as per Architecture Components. If that is not an option, you can manually store and retrieve objects using the Activities getLastNonConfigurationInstance.
#Override
public void onPause() {
/**
* store your data
*/
super.onPause();
}
#Override
public void onResume() {
/**
* retrieve your data
*/
super.onResume();
}
I have an activity that has 3 fragments on it with Tabs, one of them is called "TaskFragment".
In my main Activity i only load the fragments.
In TaskFragment i have a RecyclerView that is working fine and is showing the items as intended.
The problem comes, when i insert data using a DialogFragment, because it does insert data (i am using DbFlow ORM), but it does not (of course) refresh the adapter since it is in the TaskFragment fragment inside the DetailMainActivity activity as i said.
I have tried to use onResume() and onPause() in order to refresh the adapter, but they are never called since the activity does not get paused or in onresume for a DialogFragment.
I have tried aswell to use an interface, but it does not work and i have searched all over stackoverflow and google with no luck.
I leave here some of my code for you to understand better:
DetailMainActivity.java
Here in the onClick interface i show the DialogFragment to the user to input the information.
FragmentManager fm = getSupportFragmentManager();
AddSimpleTask sptask = new AddSimpleTask();
sptask.show(fm, "tag");
TaskFragment.java
In this fragment i have my RecyclerView
private void setupRecyclerView() {
mRecyclerView.setAdapter(adapter);
mRecyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 2));
mRecyclerView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
if (DetailMainActivity.FAB_Status) {
DetailMainActivity.hideFAB();
DetailMainActivity.FAB_Status = false;
}
return false;
}
});
}
private void setupAdapter() {
adapter = new DetailMainTaskAdapter(simpleTaskList, this);
}
AddSimpleTask
And this is my DialogFragment. I have set a setOnShowListener() in order to avoid the DialogFragment to get dismiss early.
#Override
public void onShow(DialogInterface dialogInterface) {
final AlertDialog dialog =(AlertDialog) getDialog();
if (dialog != null){
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
positiveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (mEditTextName.getText().toString().trim().isEmpty() ||
mEditTextContent.getText().toString().trim().isEmpty() ) {
if (mEditTextName.getText().toString().trim().isEmpty()) {
mEditTextName.setError("Can not be empty");
}
if (mEditTextContent.getText().toString().trim().isEmpty()) {
mEditTextContent.setError("Can not be empty");
}
}else {
presenter.beingInsertion(mEditTextName.getText().toString().trim(), mEditTextContent.getText().toString().trim()
, foreignId);
}
}
});
negativeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dismiss();
}
});
}
}
If the insert is successfully achieved the onInsertSuccess method is called (i am using MVP)
#Override
public void onInsertSuccess() {
Snackbar.make(getActivity().findViewById(R.id.containerMainDetail), "Actividad agregada", Snackbar.LENGTH_SHORT).show();
dismiss();
}
I have called adapter.notifyDataSetChanged() in many places, and i also tried with a custom interface, but i can not make this work.
Sorry for the long post, but thanks in advance for your help.
There are some errors in your statement but I'll get to that later. notifyDataSetChanged() only notifies the adapter that the underlying list (or array) has changed. The implication is that you first need to requery your database and obtain the new list before calling notifyDataSetChanged() on the adapter else there is no point as the underlying list will still be the same and it will not update the adapter.
The correct way of calling this will be through your custom listener interface and not in the onPause()/onResume() callbacks as there is the possibility that the user does not enter a value and hence you will unnecessarily be querying the database. In your custom listener interface implementation, first update the list with the new data from the DB and then notify the adapter.
Which leads to the error in assumption that onPause()/onResume() callbacks do not happen when your Activity is covered by a DialogFragment - this is incorrect. The moment the activity view is even partially covered, the onPause() callback is triggered.
I'm having some issues with the onSaveInstanceState, but it may be because I misunderstand how it works.
I have a Arraylist i want to save when I switch activities then restore when i come back.
so i have this method
#Override
protected void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
bundle.putStringArrayList("History", History);
and my onCreate
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if( savedInstanceState != null ) {
History = savedInstanceState.getStringArrayList("History");
}
}
For some reason this loop is not being initliased
if( savedInstanceState != null ) {
History = savedInstanceState.getStringArrayList("History");
}
I'm pretty sure its soemthing fairly simple but I'm still quite new to Java.
If you are simply switching activities, it will stay 'alive' in the background (unless the OS decides to kill it off to free memory). Read this article for more information on Activity state
In this case, onCreate() will not be called when the Activity resumes, as it has already been created and is simply been onResume()-ed.
You should look at another method of storing your History values such as in SharedPreferences, a database or in a persistent object on the application class instance (whatever suits your preference) in your onPause() method and restore those values in onResume().
onSavedInstanceState() is used for when an activity is killed like in an orientation change or OS kill.
I've found a ton of conflicting information regarding the proper way to restore application state when using Fragments embedded in Activities. Please let me know if my architecture is the problem because that is totally possible. My test Weather app is architected as follows.
The main activity "ReportsActivity" contains the fragment "ReportsFragment" (This is a list of the next 10 days of Weather Reports)
ReportsFragment has an onItemClickListener that launches a new Activity "WeatherDetailActivity" and passes it an intent which contains some JSON Data that I use to populate the Weather Detail UI. This data is then presented on a fragment that the WeatherDetailActivity manages.
My problem is, when the user presses the back button, the ReportsFragment has been destroyed so it runs through its full lifecycle. I've tried a number of techniques I've found online to load the activity's data from a bundle, but no matter what I've tried so far the Intents' Extras are null in the ReportsActivity's onCreate method. (Note: the reason I need to do this is to avoid firing off an API Call each time I open my main Activity which fetches weather data from Weather Underground).
I'm struggling determining what would be the best way to construct this app: Should I have a single activity that pushes and pops Fragments that it manages? Or are multiple activities that each manage their own fragments the standard practice?
At the moment here is how I'm attempting to save my application state onto the intent. I'm trying to save the state in onPostExecute from my AsyncTask so i'm on the main thread after i've fetched my results from the API Call:
#Override
protected void onPostExecute(Report[] result){
if (result != null){
ArrayList<String>reportsArrayList = new ArrayList<String>();
Gson jsonArray = new GsonBuilder().setPrettyPrinting().create();
for (int x = 0; x < result.length; x++){
reportsArrayList.add(jsonArray.toJson(result[x], Report.class));
}
mExtras.putStringArrayList(ReportsActivity.ReportsActivityState.KEY_ACTIVITY_REPORTS,reportsArrayList);
}
}
I then attempt to restore state from the ReportsActivity's onCreate Method:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_reports);
if (savedInstanceState == null) {
Intent intent = getIntent();
mFragment = ReportsFragment.newInstance(intent
.getStringArrayListExtra(ReportsActivityState.KEY_ACTIVITY_REPORTS));
getFragmentManager().beginTransaction()
.add(R.id.container, mFragment).commit();
}
}
In all cases the StringArrayListExtra I'm trying to get from the intent return null.
This could very well be me trying to solve an Android problem with an iOS mindset, but is there not an easy way to just restore the main activity to what it was before I pushed the detail view?
I think it would be worth your while taking a look at EventBus.
Basically you can define a object holder of any kind, for example:
class WeatherData {
List<String> reports;
public WeatherData(List<String> reports) {
this.reports = reports;
}
}
Now, in an Activity or Fragment in which you wish to remember the state, or pass some state to another Activity or Fragment do:
// this removes all the hazzle of creating bundles etc
EventBus.getDefault().postSticky(new WeatherData(reports));
And any where in your code your wish to know the most recent WeatherData:
WeatherData weatherData = EventBus.getDefault().getSticky(WeatherData.class);
EventBus also has nice methods for event handling (button clicks, completion of long running processes, etc..)
The library can be found here: https://github.com/greenrobot/EventBus
And some more examples here: http://awalkingcity.com/blog/2013/02/26/productive-android-eventbus/
Some suggestions without using 3. part library:
1) Calling setRetainInstance(true) in your fragments onCreate method, what this should do is to persist public variables between instances.
Though it seems it does not work for fragments on the back stack: Understanding Fragment's setRetainInstance(boolean)
2) Hand the fragment data to your Activity, something like reading/updating ((YourActivity)getActivity()).someFragmentBundle, possibly save it in onSaveInstanceState of the Activity and retrieve it in onCreate. That is, having your Activity hold the data in-between instances.
3) You could also persist the data, saving it to a file or using SharedPreferences http://developer.android.com/training/basics/data-storage/index.html
This method has the advantage that it will enable restoring the data even after a complete kill of your app.
The Architectural question
Disclaimer: subjective opinion
I would generally say keep the Activity as 'slim' as possible, holding a range of related fragments.
Thus, having multiple Activities is fine but they should each manage a set of (or a single) related fragments that are relevant for the current Activity.
It just occurred to me to check out one of the Android Studio templates that Google Provides that I often overlooked. From Google's own templates, it appears clear that the preferred method for Master Detail Activities/Fragments is to have each Fragment Managed by their own activities (as I was attempting to achieve above).
(I should note that I was able to successfully achieve to flow that I wanted using a single Activitiy with multiple fragments and customizing the Animation and forcefully showing and hiding the up button.)
PersonListActivity.java
public class PersonListActivity extends Activity
implements PersonListFragment.Callbacks {
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean mTwoPane;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_person_list);
if (findViewById(R.id.person_detail_container) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
mTwoPane = true;
// In two-pane mode, list items should be given the
// 'activated' state when touched.
((PersonListFragment) getFragmentManager()
.findFragmentById(R.id.person_list))
.setActivateOnItemClick(true);
}
// TODO: If exposing deep links into your app, handle intents here.
}
/**
* Callback method from {#link PersonListFragment.Callbacks}
* indicating that the item with the given ID was selected.
*/
#Override
public void onItemSelected(String id) {
if (mTwoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
Bundle arguments = new Bundle();
arguments.putString(PersonDetailFragment.ARG_ITEM_ID, id);
PersonDetailFragment fragment = new PersonDetailFragment();
fragment.setArguments(arguments);
getFragmentManager().beginTransaction()
.replace(R.id.person_detail_container, fragment)
.commit();
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent = new Intent(this, PersonDetailActivity.class);
detailIntent.putExtra(PersonDetailFragment.ARG_ITEM_ID, id);
startActivity(detailIntent);
}
}
}
PersonListFragment.java
public class PersonListFragment extends ListFragment {
/**
* The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets.
*/
private static final String STATE_ACTIVATED_POSITION = "activated_position";
/**
* The fragment's current callback object, which is notified of list item
* clicks.
*/
private Callbacks mCallbacks = sDummyCallbacks;
/**
* The current activated item position. Only used on tablets.
*/
private int mActivatedPosition = ListView.INVALID_POSITION;
/**
* A callback interface that all activities containing this fragment must
* implement. This mechanism allows activities to be notified of item
* selections.
*/
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
public void onItemSelected(String id);
}
/**
* A dummy implementation of the {#link Callbacks} interface that does
* nothing. Used only when this fragment is not attached to an activity.
*/
private static Callbacks sDummyCallbacks = new Callbacks() {
#Override
public void onItemSelected(String id) {
}
};
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public PersonListFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// TODO: replace with a real list adapter.
setListAdapter(new ArrayAdapter<DummyContent.DummyItem>(
getActivity(),
android.R.layout.simple_list_item_activated_1,
android.R.id.text1,
DummyContent.ITEMS));
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Restore the previously serialized activated item position.
if (savedInstanceState != null
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
}
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Activities containing this fragment must implement its callbacks.
if (!(activity instanceof Callbacks)) {
throw new IllegalStateException("Activity must implement fragment's callbacks.");
}
mCallbacks = (Callbacks) activity;
}
#Override
public void onDetach() {
super.onDetach();
// Reset the active callbacks interface to the dummy implementation.
mCallbacks = sDummyCallbacks;
}
#Override
public void onListItemClick(ListView listView, View view, int position, long id) {
super.onListItemClick(listView, view, position, id);
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mActivatedPosition != ListView.INVALID_POSITION) {
// Serialize and persist the activated item position.
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
}
}
/**
* Turns on activate-on-click mode. When this mode is on, list items will be
* given the 'activated' state when touched.
*/
public void setActivateOnItemClick(boolean activateOnItemClick) {
// When setting CHOICE_MODE_SINGLE, ListView will automatically
// give items the 'activated' state when touched.
getListView().setChoiceMode(activateOnItemClick
? ListView.CHOICE_MODE_SINGLE
: ListView.CHOICE_MODE_NONE);
}
private void setActivatedPosition(int position) {
if (position == ListView.INVALID_POSITION) {
getListView().setItemChecked(mActivatedPosition, false);
} else {
getListView().setItemChecked(position, true);
}
mActivatedPosition = position;
}
}
PersonDetailActivity.java
public class PersonDetailActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_person_detail);
// Show the Up button in the action bar.
getActionBar().setDisplayHomeAsUpEnabled(true);
// savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity
// (e.g. when rotating the screen from portrait to landscape).
// In this case, the fragment will automatically be re-added
// to its container so we don't need to manually add it.
// For more information, see the Fragments API guide at:
//
// http://developer.android.com/guide/components/fragments.html
//
if (savedInstanceState == null) {
// Create the detail fragment and add it to the activity
// using a fragment transaction.
Bundle arguments = new Bundle();
arguments.putString(PersonDetailFragment.ARG_ITEM_ID,
getIntent().getStringExtra(PersonDetailFragment.ARG_ITEM_ID));
PersonDetailFragment fragment = new PersonDetailFragment();
fragment.setArguments(arguments);
getFragmentManager().beginTransaction()
.add(R.id.person_detail_container, fragment)
.commit();
}
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
navigateUpTo(new Intent(this, PersonListActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}
PersonDetailFragment.java
public class PersonDetailFragment extends Fragment {
/**
* The fragment argument representing the item ID that this fragment
* represents.
*/
public static final String ARG_ITEM_ID = "item_id";
/**
* The dummy content this fragment is presenting.
*/
private DummyContent.DummyItem mItem;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public PersonDetailFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments().containsKey(ARG_ITEM_ID)) {
// Load the dummy content specified by the fragment
// arguments. In a real-world scenario, use a Loader
// to load content from a content provider.
mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_person_detail, container, false);
// Show the dummy content as text in a TextView.
if (mItem != null) {
((TextView) rootView.findViewById(R.id.person_detail)).setText(mItem.content);
}
return rootView;
}
}
I have an android app which is already handling changes for orientation, i.e. there is a android:configChanges="orientation" in the manifest and an onConfigurationChange() handler in the activity that switches to the appropriate layout and preps it. I have a landscape / portrait version of the layout.
The problem I face is that the activity has a dialog which could be open when the user rotates the device orientation. I also have a landscape / portrait version of the dialog.
Should I go about changing the layout of the dialog on the fly or perhaps locking the activity's rotation until the user dismisses the dialog.
The latter option of locking the app appeals to me since it saves having to do anything special in the dialog. I am supposing that I might disable the orientation when a dialog opens, such as
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
and then when it dismisses
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
Would that be a sensible thing to do? If the screen orientation did change while it was locked, would it immediately sense the orientation change when it was unlocked?
Are there alternatives?
I would recommend not turning off the screen rotation, instead of this handle the configuration changes for the Dialog. You could use one of these two approach for this:
The first one is using a flag variable in onSaveInstanceState(outState) method, and restore the dialog onCreate(bundle) method:
in this example my flag variable is called 'isShowing Dialog', when the onCreate method is called by the android System for first time, the bundle argument will be null and nothing happens. However when the activity it's recreated by a configuration change (screen rotation), the bundle will have the boolean value isShowing Dialog, previously saved by the inSaveInstanceState(...) method, so if the variable gets true the dialog is created again, the trick here is set the flag in true when the dialog get showing, and false when it's not, is a little but simple trick.
Class MyClass extends Activity {
Boolean isShowingDialog = false;
AlertDialog myDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
if(savedInstanceState!=null){
isShowingDialog = savedInstanceState.getBoolean("IS_SHOWING_DIALOG", false);
if(isShowingDialog){
createDialog();
}
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
outState.putBoolean("IS_SHOWING_DIALOG", isShowingDialog);
super.onSaveInstanceState(outState);
}
#Override
protected void onPause() {
if(myDialog!=null && myDialog.isShowing()) {
myDialog.dismiss();
}
}
private void createDialog() {
AlertDialog.Builder dialog_builder = new AlertDialog.Builder(this);
dialog_builder.setTitle("Some Title"):
... more dialog settings ...
myDialog = dialog_builder.create();
myDialog.show();
isShowingDialog = true;
}
private void hideDialog(){
myDialog.dismiss();
isShowingDialog = false;
}
}
The second approach is to use the ability of the fragments components to retain its states, the main idea is create the dialog inside a fragment, there is the problem about detach and reattach the fragment during the configuration changes (because you need dismiss and show the dialog correctly), but the solution is very similar to the first approach. The advantage of this approach is that if you have an AlertDialog with a couple of configurations, when the fragment is recreated there is not needed to create and setting up the dialog again, only make it show() and the AlertDialog state is maintained by the fragment.
I hope this helps.
I suggest your Dialog should override onSaveInstanceState() and onRestoreInstanceState(Bundle) to save its state into a Bundle.
You then override those methods in your Activity, checking if the Dialog is shown and if so - calling the dialog's methods to save and restore it's state.
If you are displaying this dialog from a fragment, you will want to override OnActivityCreated(Bundle) instead of OnRestoreInstanceState.
For a source example see the built-in clock app provided with Android, where the SetAlarm Activity handles the TimePickerDialog this way.
If you are handling orientation changes yourself, then here is an approach.
I won't claim that this is an elegant solution, but it works:
You can keep track of whether the dialog has an active instance inside the dialog class itself, by using a static variable activeInstance, and overriding onStart() to set activeInstance = this and onCancel() to set activeInstance = null.
Provide a static method updateConfigurationForAnyCurrentInstance() that tests that activeInstance variable and, if non-null, invokes a method activeInstance.reInitializeDialog(), which is a method that you will write to contain the setContentView() call plus the code that wires the handlers for the dialog controls (button onClick handlers, etc. - this is code that would normally appear in onCreate()). Following that, you would restore any displayed data to those controls (from member variables in your dialog object). So, for example, if you had a list of items to be viewed, and the user were viewing item three of that list before the orientation change, you would re-display that same item three at the end of updateConfigurationForAnyCurrentInstance(), right after re-loading the controls from the dialog resource and re-wiring the control handlers.
You would then call that same reInitializeDialog() method from onCreate(), right after super.onCreate(), and place your onCreate()-specific initialization code (e.g., setting up the list of items from which the user could choose, as described above) after that call.
This will cause the appropriate resource (portrait or landscape) for the dialog's new orientation to be loaded (provided that you have two resources defined having the same name, one in the layout folder and the other in the layout-land folder, as usual).
Here's some code that would be in a class called YourDialog:
ArrayList<String> listOfPossibleChoices = null;
int currentUserChoice = 0;
static private YourDialog activeInstance = null;
#Override
protected void onStart() {
super.onStart();
activeInstance = this;
}
#Override
public void cancel() {
super.cancel();
activeInstance = null;
}
static public void updateConfigurationForAnyCurrentInstance() {
if(activeInstance != null) {
activeInstance.reInitializeDialog();
displayCurrentUserChoice();
}
}
private void reInitializeDialog() {
setContentView(R.layout.your_dialog);
btnClose = (Button) findViewById(R.id.btnClose);
btnClose.setOnClickListener(this);
btnNextChoice = (Button) findViewById(R.id.btnNextChoice);
btnNextChoice.setOnClickListener(this);
btnPriorChoice = (Button) findViewById(R.id.btnPriorChoice);
btnPriorChoice.setOnClickListener(this);
tvCurrentChoice = (TextView) findViewById(R.id.tvCurrentChoice);
}
private void displayCurrentUserChoice() {
tvCurrentChoice.setText(listOfPossibleChoices.get(currentUserChoice));
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
reInitializeDialog();
listOfPossibleChoices = new ArrayList<String>();
listOfPossibleChoices.add("One");
listOfPossibleChoices.add("Two");
listOfPossibleChoices.add("Three");
currentUserChoice = 0;
displayCurrentUserChoice();
}
#Override
public void onClick(View v) {
int viewID = v.getId();
if(viewID == R.id.btnNextChoice) {
if(currentUserChoice < (listOfPossibleChoices.size() - 1))
currentUserChoice++;
displayCurrentUserChoice();
}
}
else if(viewID == R.id.btnPriorChoice) {
if(currentUserChoice > 0) {
currentUserChoice--;
displayCurrentUserChoice();
}
}
Etc.
Then, in your main activity's onConfigurationChanged() method, you would just invoke YourDialog.updateConfigurationForAnyCurrentInstance() whenever onConfigurationChanged() is called by the OS.
Doesn't seem the title was ever resolved (Google Necro Direct).
Here is the solution, matching the request.
When your activity is created, log the screen orientation value.
when onConfiguration change is called on your activity, compare the orientation values. if the values don't match, fire off all of your orientation change listeners, THEN record the new orientation value.
Here is some constructive code to put in your activity (or any object that can handle configuration change events)
int orientation; // TODO: record orientation here in your on create using Activity.this.getRequestedOrientation() to initialize!
public int getOrientation(){return orientation;}
public interface OrientationChangeListener {
void onOrientationChange();
}
Stack<OrientationChangeListener> orientationChangeListeners = new Stack<>();
public void addOrientationChangeListener(OrientationChangeListener ocl){ ... }
public void removeOrientationChangeListener(OrientationChangeListener ocl){ ... }
That's the basic environment. Here's your executive:
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (orientation != newConfig.orientation)
for (OrientationChangeListener ocl:orientationChangeListeners) ocl.onOrientationChange();
orientation = newConfig.orientation;
}
In YOUR code model, you may need to send the new configuration, with the event, or the two orientation values with the event. However, Activity.this.getOrientation() != Activity.this.getRequestedOrientation() during event handling (because we are in a logical state of change between two logical values).
In review of my post, i have determined that there could be some synchronization issues, with multiple events! This is not a fault of this code, but a fault of "Android Platform" for not having defacto orientation sense handlers on every window, thusly trashing the polymorphic benefits of using java in the first place..
See the answer from Viktor Valencia above. That will work perfectly with the slight adjustment that you move the createDialog() to onResume.
#Override
protected void onResume() {
super.onResume();
if(isShowingDialog){
createDialog();
}
}
Fetch the boolean isShowingDialog value at onCreate, as suggested, but wait for onResume to display the dialog.