Mantener los datos cuando rotemos la pantalla en Android

En esta entrada veremos como mantener los datos de nuestra aplicación cuando rotamos la pantalla.


  • Problema

    Cuando tenemos una aplicación y rotamos la pantalla, puede que perdamos toda la información que teníamos si no lo tratamos, ya que Android destruye y recrea la actividad que está en ejecución. Es por ello por lo que queremos guardar la información para que no perdamos los datos introducidos.

  • Solución

    Hay diferentes enfoques sobre esto. Si toda nuestra información comprende tipos primitivos, o en Strings, o es Serializable, podemos guardar su estado mediante el método onSaveInstanceState() en el Bundle que pasamos.

    Otra solución nos permite retornar un objeto singular arbitrario que implementado mediante el método onRetailNonConfigurationInstance() cerca del final del método onCreate() para ver si hay algún dato previo guardado, y entonces asignarlo.

  • Discusión

    Usando onSaveInstanceState()

    Tomando como ejemplo la APP que hicimos el otro día, la cual podemos encontrar aquí: APP – Stepper y la cual tenía este problema.

    Vamos a usar el método onSaveInstanceState() y onRestoreInstanceState() para que cuando rotemos la pantalla, la información del contador siga en como lo dejamos.

    El método onSaveInstance() podemos agregarlo una vez cerrado el método onCreate() y tomaría esta forma:

    			@Override
    			protected void onSaveInstanceState(Bundle outState){
    			 	super.onSaveInstanceState(outState);
    			   	outState.putInt("CONT", cont);
    			}
    			

    Lo que hacemos es guardar el estado de la Activity antes de ser destruida con el super.onSaveInstanceState(outState) y luego le agregamos con el método putInt() para guardar la información que queremos, en este caso queremos guardar lo que valía la variable cont que estamos usando para el TextView. Este método pide como argumento una clave, la cual he llamado “CONT”, y el valor, que en este caso es cont.

    Existen otros métodos como son putString(), putBoolean(), putChar() etc, pero en este caso guardamos un entero, por tanto hemos usado putInt().

    A continuación, una vez que tenemos este método sustituido, vamos a sustituir otro llamado onRestoreInstanceState(), el cual es el que leerá el objeto Bundle que guardamos con anterioridad y pondrá de nuevo los datos en su sitio.

    			@Override
    			protected void onRestoreInstanceState(Bundle savedInstanceState){
    				super.onRestoreInstanceState(savedInstanceState);
    			   	cont = savedInstanceState.getInt("CONT");
    			   	tvCont.setText(String.valueOf(cont));
    			}
    			

    En este método, vemos que recibimos como parámetros un objeto Bundle, que en este caso es el que creamos en el método onSaveInstanceState(), el cual trae nuestra información guardada.

    Lo primero que hacemos, es restaurar el estado mediante el método savedInstanceState(savedInstanceState), y luego le aplicamos a la variable cont el valor que tenía anteriormente, mediante la llamada al método getInt() que tiene el objeto savedInstanceState, y pasando como argumento la clave que pusimos, en este caso “CONT”.

    Una vez hecho esto, volvemos a establecer al TextView el valor que tenía, y ahora si rotamos nuestra aplicación, tendremos la misma información que antes teníamos.


Ahora podemos pasar de nuevo a probar la aplicación, o implementar estos métodos en alguna existente y veremos si esto funciona, os pongo las capturas de mi emulador para que veais cual es mi resultado:

Cualquier aporte o corrección es bienvenido.

Saludos!!!

  • dani_91

    ¿Se podría salvar el valor de más de una variable con este método?

    • Buenos días dani_91.

      Si puedes, si te fijas es como cuando pasas variables de una Activity a otra. Lo único que hay que tener en cuenta es que va por clave-valor o key-value, como lo quieras llamar.

      Te he hecho un ejemplo básico en el que guardo el texto de 4 EditText, que aunque se puede simplificar, pero se vé mejor:

      public class MainActivity extends Activity {
      	private EditText et1, et2, et3, et4;
      
      	@Override
      	protected void onCreate(Bundle savedInstanceState) {
      		super.onCreate(savedInstanceState);
      		setContentView(R.layout.activity_main);
      		
      		et1 = (EditText)findViewById(R.id.editText1);
      		et2 = (EditText)findViewById(R.id.editText2);
      		et3 = (EditText)findViewById(R.id.editText3);
      		et4 = (EditText)findViewById(R.id.editText4);
      	}
      
      	@Override
      	protected void onSaveInstanceState(Bundle outState) {
      		// TODO Auto-generated method stub
      		super.onSaveInstanceState(outState);
      		String edit1 = et1.getText().toString(); 
      		String edit2 = et2.getText().toString();
      		String edit3 = et3.getText().toString();
      		String edit4 = et4.getText().toString();
      		
      		outState.putString("STRING1", edit1);
      		outState.putString("STRING2", edit2);
      		outState.putString("STRING3", edit3);
      		outState.putString("STRING4", edit4);
      		
      	}
      
      	@Override
      	protected void onRestoreInstanceState(Bundle savedInstanceState) {
      		// TODO Auto-generated method stub
      		super.onRestoreInstanceState(savedInstanceState);
      		String edit1 = savedInstanceState.getString("STRING1");
      		String edit2 = savedInstanceState.getString("STRING2");
      		String edit3 = savedInstanceState.getString("STRING3");
      		String edit4 = savedInstanceState.getString("STRING4");
      		
      		et1.setText(edit1);
      		et2.setText(edit2);
      		et3.setText(edit3);
      		et4.setText(edit4);
      	}
      
      }
      

      Si por otro lado, quieres almacenar un objeto de alguna clase que tengas, yo el único modo que he probado es implementando la interface Parcelable en la clase que quiero almacenar, aunque seguramente haya otros.

      Saludos!!

  • beto

    si quisiera guardar una imagen com oseria ???

    • Puedes hacerlo con la clase Bitmap, ya que implementa la interface Parcelable, por tanto solo deberías hacer:

      outState.putParcelable("IMAGEN", bitmap);
      

      Donde “IMAGEN” es la key, y bitmap la referencia al objeto.

      Saludos!!!

      • beto

        la imagen la tengo en un ImageView com ola paso a bitmap

        Bitmap img = pbImagen.get??????? la paso a bitmap

        outState.putParcelableArr(“DESC”, img); la guardo

  • beto

    lo puse asi
    Bitmap img = pbImagen.getDrawingCache();

    espero y funcione gracias

    • También puedes usar lo siguiente:

      Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
      

      Saludos!

      • beto

        si em sirvio
        como lo tenia no funciona
        gracias

      • muy buenas, yo estoy cogiendo de mi imageview la imagen que previamente me descargo, la convierto a bitmap:
        Bitmap bitmap = ((BitmapDrawable) MiImageView.getDrawable()).getBitmap();
        outState.putParcelable(“IMAGEN”, bitmap);

        en el restore:
        imagencache = savedInstanceState.getParcelable(“IMAGEN”);

        Y luego para asignarla tengo problemas(no me funciona) he probado:

        Intent intent = new Intent(this, MainActivity.class);
        intent.putExtra(“BitmapImage”, imagencache);
        Bitmap bitmap = (Bitmap) intent.getParcelableExtra(“BitmapImage”);
        // Deserialize Parcelable and cast to Bitmap first:
        m_imageView.setImageBitmap(bitmap);
        La verdad que no se como hacer, cuando giro el telefono vuelve a cargar una imagen que se baja de mi servidor. Tarda cuestion de segundos pero me gustaria no tener que volver a bajarla(y consumir datos alegremente, aunque son de pocos kbytes).

        Bueno a ver si doy con la solucion! Un saludo

        • Buenas inachomsky,

          Esta fracción de código:

          Intent intent = new Intent(this, MainActivity.class);
          intent.putExtra(“BitmapImage”, imagencache);
          

          La usaría para pasar un Bitmap de una Activity a otra, pero no para mantener la imagen cuando rotemos la pantalla.

          Cuando se rota la pantalla, se hace comprobando en el método onCreate si el objeto Bundle es null o no.

          Si es null, quiere decir que tu imagen no se ha guardado, y si no es null, puedes extraer entonces el Bitmap con la fracción de código:

          imagencache = savedInstanceState.getParcelable(AQUÏ_LA_KEY);
          

          Espero que te sea de ayuda y por cualquier duda aquí estoy.

          Saludos!

  • luis amador garcia

    funcionaria con notificaciones de la barra de estado???, yo tengo un reproductor streaming en segundo plano pero cuando lo mando llamar desde la barra de notificaciones deja de funcionar, talvez es porque no esta guardando la url de trasmicion (porque la tengo como variable)

  • Gracias por la rapida respuesta!
    Es lo que pensaba( no entendia muy bien el codigo cogi esa parte de una web), creia que mi problema podia ser la manera en que cojo la imagen(ya que la descargo, la situo en el imageview, y luego la guardo sacandola del imageview, no se aun como funciona android con las descargas y si esto esta bien hecho)

    Ahora tengo esto:
    @Override
    protected void onSaveInstanceState(Bundle outState){
    super.onSaveInstanceState(outState);
    //guardamos la informacion relevante
    Bitmap bitmap = ((BitmapDrawable) m_imageView.getDrawable()).getBitmap();
    outState.putParcelable(“IMAGEN”, bitmap);
    }
    @Override
    public void onRestoreInstanceState(Bundle savedInstanceState)
    {
    super.onRestoreInstanceState(savedInstanceState);
    imagencache = savedInstanceState.getParcelable(“IMAGEN”);
    }

    Y creo que el problema esta aqui:
    en el main creo la variable
    private Parseable imagencache;

    (en el on create)

    la asigno asi:

    Bitmap bitmap = (Bitmap) savedInstanceState.getParcelable(“imagecache”);
    // Deserialize Parcelable and cast to Bitmap first:
    m_imageView.setImageBitmap(bitmap);

    Creo que estoy fallando al asignar la imagen en el m_imageView, o tal vez en el tipo de variable “imagencache”.
    Bueno, muchisimas gracias por la ayuda un detallazo!.

    • Vale ya encontre el error,
      en el oncreate
      en ved de
      Bitmap bitmap = (Bitmap) savedInstanceState.getParcelable(“imagecache”);
      es

      Bitmap bitmap = (Bitmap) savedInstanceState.getParcelable(“IMAGEN”);

      Me siento un poco cazurro jajaja, bueno muchas gracias, aqui queda el codigo por si alguien trata de hacer lo mismo sin saber como 🙂

      • Hola tenemos un problema con nuestro proyecto.
        Nos puedes ayudar?

        • Buenas francisco,

          ¿Qué problema tienes?

          Saludos

  • Enmanuel

    ¿Se puede guardar un arraylist?

    • Buenas Enmanuel,

      Efectivamente se puede pasar un ArrayList, y una de las soluciones mas fáciles es que el tipo de dato que maneje el ArrayList implemente la Interface Serializable o Parcelable, ya que así en un objeto de la clase Intent puedes usar los métodos putParcelableArrayListExtra(name, value) en el caso de que la clase del objeto implemente esta Interface (lo cuál es basatnte fácil :)). Si por otro casual quieres pasar un ArrayList de String, puedes usar el método putStringArrayListExtra(name, value).

      Por ponerte un ejemplo, si tienes una clase Item y esta clase implementa la Interface Parcelable, al hacer un ArrayList ya podrás pasarlo a la siguiente Activity.

      Espero que te sea de ayuda Enmanuel,

      Saludos!

  • beto

    buenas tardes
    tengo una app en android en la qe ocupo que al rotar la pantalla esta conserve todos los datos, algunos de los datos los guardo en SQLite, sharedPreferences, etc, pero tengo adapters personalizados con los cuales lleno algunas listview o combobox para seleccionar una opcion y estos no se de que manera puedo almacenarlos, ademas de qe mi app consta de varias activityes y si roto la pantalla enalguna de estas las demas tamb se ven afectadas ya que cuando regreso a ellas los datos se borran de pantalla
    espero puedas ayudarme
    saludos

    • beto

      ademas de clases personalizadas como tamb instancias de sqlite

      • Buenos días beto,

        Los adapters personalizados no son más que adapter que se adaptan a una clase que tengas creada, por tanto, si esa clase implementa la interface Parcelable puedes guardar esos objetos en memoria para cuando hay rotación, cargar de nuevo el Arrayilst donde tengas almacenados esos objetos, y ya luego crear de nuevo el Adapter para cargar de nuevo todo tal como estaba.

        Las instancias de SQLite deberías hacer que implementen el patrón Singleton el cual logra que solo puedas tener una instancia, por tanto, estés en la Activity que estés, siempre tendrás la misma instancia y podrás trabajar con ello.

        Espero que te sirva de guia 😉

        Saludos!

  • Enmanuel

    Hola David!
    He intentado guardar la posicion de un item seleccionado en unos spinner pero no ha funcionado. El código es este.

    @Override
    protected void onSaveInstanceState(Bundle outState){
    super.onSaveInstanceState(outState);
    outState.putInt(“proyecto”, proy.getSelectedItemPosition());
    outState.putInt(“situacion”, sit.getSelectedItemPosition());
    outState.putInt(“tipoi”, inc.getSelectedItemPosition());
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState){
    super.onRestoreInstanceState(savedInstanceState);
    proy.setSelection(savedInstanceState.getInt(“proyecto”));
    sit.setSelection(savedInstanceState.getInt(“situacion”));
    inc.setSelection(savedInstanceState.getInt(“tipoi”));

    }

    Espero puedas ayudarme.

    • Buenas tardes Enmanuel,

      El código a primera vista está bien, por lo que el error puede darse en el momento en el que se cargan los Spinner inicialmente, porque como habrás comprobado el método onCreate se inicia antes que el método onRestoreInstanceState, por tanto sería comprobar si para cuando llega a onRestoreInstanceState ya hay algo en esos Spinner.

      Por otro lado, no te funciona porque te da algún tipo de error?

      Saludos!

  • Enmanuel

    No da ningún error. Lo único que pasa es que, al rotar la pantalla, el elemento seleccionado es el primero, es decir, la posición 0.

    • Buenas Enmanuel,

      Por lo que puedo ver también, tienes 3 Spinner, ¿Son secuenciales?, es decir, eligiendo algo en el 1º se carga algo del 2º, y según el 2º se carga el 3º?, si es así puede ser que solo necesitaras guardar el estado del 1º. Es una suposición, pero en principio tu código es correcto :S

      Otra cosa que puedes probar es recuperar esos valores al final del onCreate, haciendo la comprobación de comprobar si el objeto savedInstanceState es null o no.

      Saludos!

      • Enmanuel

        No son secuenciales.
        Sl final del onCreate é colocado esto “Log.i(“spinner”, proy.getSelectedItem().toString());” y como tu dices, me manda un error de nullPointerException.
        ¿Qué puedo hacer?

      • Enmanuel

        *he (Como se me ha ido la pinza). Saludos!

        • Buenas Enmanuel,

          Has probado a sacar por el Log el valor del objeto Bundle?.

          Si te dá NullPointerException es porque parece ser que no tiene nada, es mas, el spinner no está instanciado y es null, revisa biene l orden en el que se ejecuta todo y pon salidas en el Log antes y después de las operaciones que crees que fallan, eso te ayudará a saber mas o menos exactamente hasta donde llega y localizar el error.

          Espero sirva de ayuda 😉

          Saludos!

  • Andres Rodriguez

    Hola Enmanuel, tengo exactamente el mismo problema tuyo, tengo un Spinner que se carga de datos que vienen de un JSON en un servidor externo. Justamente dichos datos son cargados en el método onStart(), el problema surge cuando selecciono un ítem de mi Spinner, automáticamente es visible el ítem que seleccioné, pero cuando volteo el dispositivo, este ítem seleccionado deja de ser visible, y el primer ítem de mi lista vuelve a posicionarse en el primer lugar, como si no hubiese seleccionado el item de mi Spinner. Todavía no doy la solución, por favor, si llegas a la solución mucho antes que yo, notificamelo. Gracias

  • Fabián

    Hola David, yo estoy intentando hacer lo mismo pero con una libreria para un libro para dispositivo movil, esta libreria trabaja con un setadapter, y al rotar el dispositivo necesito que se mantenga en la pagina en donde estaba antes de rotar el dispositivo y obviamente tambien mantener su contenido, intente hacerlo con el onConfigurationChanged y logro el efecto deseado, pero lo malo es que al dar vuelta a la otra pagina como que se desconfigura todo al principio, se ve mal y cuesta girar de pagina y al girar 2 paginas recien se adapta. Espero que puedas ayudarme en algo y ojala haya explicado bien mi problema,

    Saludos.

  • javier

    Hola, tengo el problema de que al rotar pierdo los datos de un array, e intentado adaptar este ejemplo que comentavas pero no me esta saliendo tengo esto para guardar el array.

    @Override
    protected void onSaveInstanceState(Bundle outState){
    super.onSaveInstanceState(outState);
    outState.putParcelable(“ARRAY”, (Parcelable) listaContactos);

    setContentView(R.layout.activity_main);

    //Obtenemos una referencia a los controles de la interfaz
    InpNombre = (EditText) findViewById (R.id.InputName);
    botonADD = (Button)findViewById(R.id.botonADD);
    listView = (ListView) findViewById(R.id.listView);
    listaContactos = new ArrayList();
    //(EntradaLista, es una clase para tratar los datos a insertar la lista junto con la clase adaptador)

    … mi codigo …
    }

    y este para recuperar.

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState){
    super.onRestoreInstanceState(savedInstanceState);

    listaContactos = savedInstanceState.getParcelable(“ARRAY”);
    adapter.notifyDataSetChanged();
    }

    el primer problema es que al arrancar la aplicacion muchas veces no aparece mi layout de botones con la lista. el segundo es que al rotar me da un error.

    que me esta fallando.

  • nancy toledo

    hola amigo, tengo un problema con el codigo, pues yo lo implemente tu ejemplo cuando ago cambui de activity y el problema es el siguiente:

    outState.putString(“STRING1”, etnombrecomandante); -> me indica que cambie el putString
    outState.putString(“STRING2”, etcorporacion);
    outState.putString(“STRING3”, etnarracion);

    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onRestoreInstanceState(savedInstanceState);
    String Stretnombrecomandante = savedInstanceState.getString(“STRING1”);
    String Stretcorporacion = savedInstanceState.getString(“STRING2”);
    String Stretnarracion = savedInstanceState.getString(“STRING3”);

    etnombrecomandante.setText(etnombrecomandante); —>me indica error en el settext
    etcorporacion.setText(etcorporacion);
    etnarracion.setText(etnarracion);