31

I have a Login screen which consists of 2 EditTexts for Username and Password. My requirement is that on orientation change , input data(if any) in EditText should remain as it is and a new layout should also be drawn. I have 2 layout xml files- one in layout folder and other in layout-land folder. I am trying to implement following 2 approaches but none of them is perfect:

(1) configChanges:keyboardHidden - In this approach, I don't provide "orientation" in configChanges in manifest file. So I call setContentView() method in both onCreate() and onConfigurationChanged() methods. It fulfills both my requirements. Layout is changed and input data in EditTexts also remains as it is. But it has a big problem :

When user clicks on Login button, a ProgressDialog shows until server-response is received. Now if user rotates the device while ProgressDialog is running, app crashes. It shows an Exception saying "View cannot be attached to Window." I have tried to handle it using onSaveInstanceState (which DOES get called on orientation change) but app still crashes.

(2) configChanges:orientation|keyboardHidden - In this approach, I provide "orientation" in manifest. So now I have 2 scenarios:

(a) If I call setContentView() method in both onCreate() and onConfigurationChanged(), Layout is changed accordingly but EditText data is lost.

(b) If I call setContentView() method in onCreate() , but not in onConfigurationChanged(), then EditText data is not lost but layout also not changes accordingly.

And in this approach, onSaveInstanceState() is not even called.

So I am in a really intimidating situation. Is there any solution to this problem? Please help. Thanx in advance.

Stefan
  • 5,203
  • 8
  • 27
  • 51
Yogesh Somani
  • 2,624
  • 3
  • 21
  • 34

12 Answers12

88

By default, Edittext save their own instance when changing orientation.

Be sure that the 2 Edittexts have unique IDs and have the same IDs in both Layouts.

That way, their state should be saved and you can let Android handle the orientation change.

If you are using a fragment, be sure it has a unique ID also and you dont recreate it when recreating the Activity.

Yalla T.
  • 3,707
  • 3
  • 23
  • 36
  • 1
    What do you mean when you say make sure a "fragment has a unique ID"? – Graeme Aug 21 '13 at 14:47
  • Every View has an ID ( EditText and Fragments are both Views). You want to make sure that this ID is not used by any other View, because this can lead to problems when trying to save&restore the state automatically. – Yalla T. Aug 23 '13 at 13:22
  • 3
    What if you use a ListView? Are you out of luck because each item does not have a unique ID? –  Jun 01 '14 at 23:30
  • In case `EditText` is constructed in java code then id can be set by predefined resource xml id `` an in java `editText.setId(R.id.some_id)` – Saqib Apr 21 '15 at 21:18
  • I create two xml files, one in activity_main other is activity_main_land. Both have 2 Edit text fields. Both have ids and in both xml ids are same. When onConfigchange() method is called I change xml file. Now when I rotate device Edit text lost the data. Can you Please help me to get rid this problem. – Shivang Nov 25 '15 at 11:55
  • @Shivang, for that specific case, you should have your activity_main layout in the `res/layout` folder and another activity_main layout (same name) in the `res/layout-land`. The system will then take care of switching between the two for you. And, you wouldn't need to add anything to the AndroidManifest. – Anonsage Feb 28 '16 at 21:26
  • It's not always true. If you override onSaveInstanceState() method and forgot to call super.onSaveInstanceState() then the default behaviour will not work. Read here in more detail https://stackoverflow.com/questions/151777/how-do-save-an-android-activity-state-using-save-instance-state/54124841#54124841 – Rohit Singh Feb 19 '19 at 04:37
34

A better approach is to let android handle the orientation change. Android will automatically fetch the layout from the correct folder and display it on the screen. All you need to do is to save the input values of the edit texts in the onSaveInsanceState() method and use these saved values to initialize the edit texts in the onCreate() method.
Here is how you can achieve this:

@Override
protected void onCreate (Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.login_screen);
    ...
    ...
    String userName, password;
    if(savedInstanceState!=null)
    {
        userName = savedInstanceState.getString("user_name");
        password= savedInstanceState.getString("password");
    }

    if(userName != null)
        userNameEdtTxt.setText(userName);
    if(password != null)
        passEdtTxt.setText(password);
}

>

@Override
    protected void onSaveInstanceState (Bundle outState)
    {
        outState.putString("user_name", userNameEdtTxt.getText().toString());
        outState.putString("password",  passEdtTxt.getText().toString());
    }
karn
  • 5,963
  • 3
  • 22
  • 29
  • what if i'm handling oriantation from android manifest, with onConfigurationChanged() method, will onCreate be called again? – Tamim Attafi Apr 06 '18 at 10:59
  • Does this preserve cursor location and text selection in each EditText? The unique ID approach described by Yalla T. does preserve cursor location and text selection. – Ted Henry Jan 24 '19 at 05:52
11

Give the element an id and Android will manage it for you.

android:id="@id/anything"
Carlos López Marí
  • 1,432
  • 3
  • 18
  • 45
9

in onConfigurationChanged method, first get the data of both the edit texts in global variables and then call setContentView method. Now set the saved data again into the edit texts.

Arpit Raniwala
  • 235
  • 2
  • 9
  • Does this preserve cursor location and text selection in each EditText? The unique ID approach described by Yalla T. does preserve cursor location and text selection. – Ted Henry Jan 24 '19 at 05:52
2

There are many ways to do this. The simplest is 2(b) in your question. Mention android:configChanges="orientation|keyboardHidden|screenSize" in your manifest so that Activity doesn't get destroyed on Orientation changes.

Call setContentView() in onConfigChange(). but before calling setContentView() get the EditText data into a string and set it back after calling setContentView()

 @Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    mEditTextData = mEditText.getText().tostring();//mEditTextData is a String 
                                                   //member variable
    setContentView(R.layout.myLayout);
    initializeViews();
}

private void initializeViews(){
    mEditText = (EditText)findViewById(R.id.edittext1);
    mEdiText.setText(mEditTextData);
}
sujith
  • 2,421
  • 2
  • 17
  • 26
2

The following should work and is standard to the activities and fragments

@Override
public void onSaveInstanceState (Bundle outState) 
{
     outState.putString("editTextData1", editText1.getText().toString());
     outState.putString("editTextData2", editText2.getText().toString());

     super.onSaveInstanceState(outState);
}

@Override
public void onCreate(Bundle savedInstanceState)
{
      super.onCreate();

      ... find references to editText1, editText2

      if (savedInstanceState != null)
      {
           editText1.setText(savedInstanceState.getString("editTextData1");
           editText2.setText(savedInstanceState.getString("editTextData2");
      }
}
chris-tulip
  • 1,840
  • 1
  • 15
  • 22
2

Im restoring instance to restore values and it works fine for me :)

@Override
protected void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    setContentView(R.layout.addtask2);
    if(savedInstanceState!=null)
     onRestoreInstanceState(savedInstanceState);

}
Aswin Anand
  • 242
  • 1
  • 11
1

Remove android:configChanges attribute from the menifest file and let android handle the orientation change your data in edittext will automatically remain.

Now The problem you mentioned is with the progress dialog force close this is because when the orientation is changed the thread running in backgroud is trying to update the older dialog component whihc was visible. You can handle it by closing the dialog on savedinstancestate method and recalling the proceess you want to perform onRestoreInstanceState method.

Below is a sample hope it helps solving your problem:-

public class MyActivity extends Activity {
    private static final String TAG = "com.example.handledataorientationchange.MainActivity";
    private static ProgressDialog progressDialog;
    private static Thread thread;
    private static boolean isTaskRunnig;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
        setContentView(R.layout.main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new EditText.OnClickListener() {

            @Override
            public void onClick(View v) {
                perform();
                isTaskRunnig = true;
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    public void perform() {
        Log.d(TAG, "perform");
        progressDialog = android.app.ProgressDialog.show(this, null,
                "Working, please wait...");
        progressDialog
                .setOnDismissListener(new DialogInterface.OnDismissListener() {

                    @Override
                    public void onDismiss(DialogInterface dialog) {
                        //isTaskRunnig = false;
                    }
                });
        thread = new Thread() {
            public void run() {
                Log.d(TAG, "run");
                int result = 0;
                try {

                    // Thread.sleep(5000);
                    for (int i = 0; i < 20000000; i++) {

                    }
                    result = 1;
                    isTaskRunnig = false;
                } catch (Exception e) {
                    e.printStackTrace();
                    result = 0;
                }
                Message msg = new Message();
                msg.what = result;
                handler.sendMessage(msg);
            };
        };
        thread.start();
    }

    // handler to update the progress dialgo while the background task is in
    // progress
    private static Handler handler = new Handler() {

        public void handleMessage(Message msg) {
            Log.d(TAG, "handleMessage");
            int result = msg.what;
            if (result == 1) {// if the task is completed successfully
                Log.d(TAG, "Task complete");
                try {
                    progressDialog.dismiss();
                } catch (Exception e) {
                    e.printStackTrace();
                    isTaskRunnig = true;
                }

            }

        }
    };

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.d(TAG, "onRestoreInstanceState" + isTaskRunnig);
        if (isTaskRunnig) {
            perform();

        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.d(TAG, "onSaveInstanceState");
        if (thread.isAlive()) {
            thread.interrupt();
            Log.d(TAG, thread.isAlive() + "");
            progressDialog.dismiss();
        }

    }
Stefan
  • 5,203
  • 8
  • 27
  • 51
Chandrashekhar
  • 498
  • 4
  • 19
  • don't use `static` to retain references, [Activity#onRetainNonConfigurationInstance()](https://developer.android.com/reference/android/app/Activity.html#onRetainNonConfigurationInstance%28%29) or the retain fragment approach described [here](https://developer.android.com/training/displaying-bitmaps/cache-bitmap.html#config-changes) exist for that purpose – zapl Sep 20 '12 at 11:13
1

As pointed out by Yalla T it is important to not recreate the fragment. The EditText will not lose its content if the existing fragment is reused.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // setContentView(R.layout.activity_frame);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    // Display the fragment as the main content.
    // Do not do this. It will recreate the fragment on orientation change!
    // getSupportFragmentManager().beginTransaction().replace(android.R.id.content, new Fragment_Places()).commit();

    // Instead do this
    String fragTag = "fragUniqueName";
    FragmentManager fm = getSupportFragmentManager();
    Fragment fragment = (Fragment) fm.findFragmentByTag(fragTag);
    if (fragment == null)
        fragment = new Fragment_XXX(); // Here your fragment
    FragmentTransaction ft = fm.beginTransaction();
    // ft.setCustomAnimations(R.xml.anim_slide_in_from_right, R.xml.anim_slide_out_left,
    // R.xml.anim_slide_in_from_left, R.xml.anim_slide_out_right);
    ft.replace(android.R.id.content, fragment, fragTag);
    // ft.addToBackStack(null); // Depends on what you want to do with your back button
    ft.commit();

}
Gunnar Bernstein
  • 6,074
  • 2
  • 45
  • 67
1

enter image description here

Saving state = Saving (Fragment State + Activity State)

When it comes to saving the state of a Fragment during orientation change, I usually do this way.

1) Fragment State:

Save and Restore EditText value

// Saving State

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putString("USER_NAME", username.getText().toString());
    outState.putString("PASSWORD", password.getText().toString());
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.user_name_fragment, parent, false);

    username = (EditText) view.findViewById(R.id.username);
    password = (EditText) view.findViewById(R.id.password);


// Retriving value

    if (savedInstanceState != null) {
        username.setText(savedInstanceState.getString("USER_NAME"));
        password.setText(savedInstanceState.getString("PASSWORD"));
    }

    return view;
}

2) Activity State::

Create a new Instance when the activity launches for the first time else find the old fragment using a TAG and the FragmentManager

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);

    fragmentManager = getSupportFragmentManager();

    if(savedInstanceState==null) {
        userFragment = UserNameFragment.newInstance();
        fragmentManager.beginTransaction().add(R.id.profile, userFragment, "TAG").commit();
    }
    else {
        userFragment = fragmentManager.findFragmentByTag("TAG");
    }

}

You can see the the full working code HERE

Rohit Singh
  • 16,950
  • 7
  • 90
  • 88
1

Below code is work for me. Need to care two things.

  1. Each Input Field (Edit Text or TextInputEditText) assign unique id.
  2. Manifest activity declaration should have on configuration change attribute with below values.

    android:configChanges="orientation|keyboardHidden|screenSize"

Sample activity declaration in manifest.

<activity
      android:name=".screens.register.RegisterActivity"
      android:configChanges="orientation|keyboardHidden|screenSize"
      android:exported="true"
      android:label="Registration"
      android:theme="@style/AppTheme.NoActionBar" />

Sample declaration of

 <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/inputLayout"
        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:boxCornerRadiusBottomEnd="@dimen/boxCornerRadiusDP"
        app:boxCornerRadiusBottomStart="@dimen/boxCornerRadiusDP"
        app:boxCornerRadiusTopEnd="@dimen/boxCornerRadiusDP"
        app:boxCornerRadiusTopStart="@dimen/boxCornerRadiusDP">

        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/inputEditText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:focusable="true"
            android:fontFamily="@font/proxima_nova_semi_bold"
            android:inputType="textCapWords"
            android:lines="1"
            android:textColor="@color/colorInputText"
            android:textColorHint="@color/colorInputText" />
    </com.google.android.material.textfield.TextInputLayout>
Hitesh Dhamshaniya
  • 2,088
  • 2
  • 16
  • 23
-2

this may help you

if your android:targetSdkVersion="12" or less

android:configChanges="orientation|keyboardHidden">

if your android:targetSdkVersion="13" or more

android:configChanges="orientation|keyboardHidden|screenSize">
Priyank Patel
  • 12,244
  • 8
  • 65
  • 85
Rajendra
  • 1,700
  • 14
  • 17