Thursday, March 14, 2013

Retaining data during orientation change, with onRetainNonConfigurationInstance() and getLastNonConfigurationInstance()

When device orientation change, Android restarts the running Activity (onDestroy() is called, followed by onCreate()). consider the case of last exercise of "Get Yahoo! Weather RSS Feed from internet"; everytime orientation change, it will reload from Yahoo Weather RSS Feed. For sure, it isn't what you expect.

In order to retaining data during orientation change, save data by overriding onRetainNonConfigurationInstance() method, it will be called by the system, as part of destroying an activity due to a configuration change. Then call getLastNonConfigurationInstance() to recover your data, when your activity is created again.

Please notice that onRetainNonConfigurationInstance() and getLastNonConfigurationInstance() were deprecated in API level 13 (Use the new Fragment API setRetainInstance(boolean) instead; this is also available on older platforms through the Android compatibility package). ~ in next exercise

Retaining data during orientation change, with onRetainNonConfigurationInstance() and getLastNonConfigurationInstance()


Modify MainActivity.java from last exercise. In order to make it easy to monitor the status, I insert some delay in the code with Thread.sleep().

package com.example.androidyahooweatherdom;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import android.os.Bundle;
import android.app.Activity;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {
 
 TextView weather, status;
 MyWeather weatherResult;
 
 class MyWeather{
  String description;
  String city;
  String region;
  String country;

  String windChill;
  String windDirection;
  String windSpeed;

  String sunrise;
  String sunset;

  String conditiontext;
  String conditiondate;
  
  String numberOfForecast;
  String forecast;

  public String toString(){
   
   return "\n- " + description + " -\n\n"
    + "city: " + city + "\n"
    + "region: " + region + "\n"
    + "country: " + country + "\n\n"

    + "Wind\n"
    + "chill: " + windChill + "\n"
    + "direction: " + windDirection + "\n"
    + "speed: " + windSpeed + "\n\n"

    + "Sunrise: " + sunrise + "\n"
    + "Sunset: " + sunset + "\n\n"

    + "Condition: " + conditiontext + "\n"
    + conditiondate +"\n"
    
    + "\n"
    + "number of forecast: " + numberOfForecast + "\n"
    + forecast;
   
  } 
 }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        weather = (TextView)findViewById(R.id.weather);
        status = (TextView)findViewById(R.id.status);
        
        status.setText("onCreate");
        
        //check if data saved
        final MyWeather data = (MyWeather)getLastNonConfigurationInstance();
        if (data == null) {
         
         status.setText("Load new data");
         
         Thread myThread = new Thread(new Runnable(){

       @Override
       public void run() {

        try {
         Thread.sleep(1000);
        } catch (InterruptedException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
        }
        
        String weatherString = QueryYahooWeather();
        
        try {
         Thread.sleep(1000);
        } catch (InterruptedException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
        }
        
              Document weatherDoc = convertStringToDocument(weatherString);
              
              try {
         Thread.sleep(1000);
        } catch (InterruptedException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
        }
              
              weatherResult = parseWeather(weatherDoc);
              
              try {
         Thread.sleep(1000);
        } catch (InterruptedException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
        }
              
              runOnUiThread(new Runnable(){

         @Override
         public void run() {
          weather.setText(weatherResult.toString());
          status.setText("Finished");
         }});
              
       }});
            myThread.start();
        }else{
         weatherResult = data;
         weather.setText(weatherResult.toString());
   status.setText("Load saved data");
        }
        
        
    }

    @Override
 @Deprecated
 public Object onRetainNonConfigurationInstance() {
     final MyWeather data = weatherResult;
        return data;
 }



 private MyWeather parseWeather(Document srcDoc){
     
     MyWeather myWeather = new MyWeather();

     //<description>Yahoo! Weather for New York, NY</description>
     myWeather.description = srcDoc.getElementsByTagName("description")
       .item(0)
       .getTextContent();

     //<yweather:location.../>
     Node locationNode = srcDoc.getElementsByTagName("yweather:location").item(0);
     myWeather.city = locationNode.getAttributes()
       .getNamedItem("city")
       .getNodeValue()
       .toString();
     myWeather.region = locationNode.getAttributes()
       .getNamedItem("region")
       .getNodeValue()
       .toString();
     myWeather.country = locationNode.getAttributes()
       .getNamedItem("country")
       .getNodeValue()
       .toString();

     //<yweather:wind.../>
     Node windNode = srcDoc.getElementsByTagName("yweather:wind").item(0);
     myWeather.windChill = windNode.getAttributes()
       .getNamedItem("chill")
       .getNodeValue()
       .toString();
     myWeather.windDirection = windNode.getAttributes()
       .getNamedItem("direction")
       .getNodeValue()
       .toString();
     myWeather.windSpeed = windNode.getAttributes()
       .getNamedItem("speed")
       .getNodeValue()
       .toString();

     //<yweather:astronomy.../>
     Node astronomyNode = srcDoc.getElementsByTagName("yweather:astronomy").item(0);
     myWeather.sunrise = astronomyNode.getAttributes()
       .getNamedItem("sunrise")
       .getNodeValue()
       .toString();
     myWeather.sunset = astronomyNode.getAttributes()
       .getNamedItem("sunset")
       .getNodeValue()
       .toString();

     //<yweather:condition.../>
     Node conditionNode = srcDoc.getElementsByTagName("yweather:condition").item(0);
     myWeather.conditiontext = conditionNode.getAttributes()
       .getNamedItem("text")
       .getNodeValue()
       .toString();
     myWeather.conditiondate = conditionNode.getAttributes()
       .getNamedItem("date")
       .getNodeValue()
       .toString();
     
     //Added to get elements of <yweather:forecast.../>
     NodeList forecastList = srcDoc.getElementsByTagName("yweather:forecast");
     
     myWeather.forecast = "";
     if(forecastList.getLength() > 0){
      myWeather.numberOfForecast = String.valueOf(forecastList.getLength());
      for(int i = 0; i < forecastList.getLength(); i++){
       Node forecastNode = forecastList.item(i);
       myWeather.forecast +=
         forecastNode
          .getAttributes()
          .getNamedItem("date")
          .getNodeValue()
          .toString() + " " +
         forecastNode
          .getAttributes()
          .getNamedItem("text")
          .getNodeValue()
          .toString() +
         " High: " + forecastNode
             .getAttributes()
             .getNamedItem("high")
             .getNodeValue()
             .toString() +
         " Low: " + forecastNode
             .getAttributes()
             .getNamedItem("low")
             .getNodeValue()
             .toString() + "\n";
      }
     }else{
      myWeather.numberOfForecast = "No forecast";
     }
     
     return myWeather; 
    }
    
    private Document convertStringToDocument(String src){
     
     Document dest = null;
     DocumentBuilderFactory dbFactory =
       DocumentBuilderFactory.newInstance();
     DocumentBuilder parser;
     
     try {
      parser = dbFactory.newDocumentBuilder();
      dest = parser.parse(new ByteArrayInputStream(src.getBytes())); 
     } catch (ParserConfigurationException e1) {
      e1.printStackTrace();
      Toast.makeText(MainActivity.this,
        e1.toString(), Toast.LENGTH_LONG).show(); 
     } catch (SAXException e) {
      e.printStackTrace();
      Toast.makeText(MainActivity.this,
        e.toString(), Toast.LENGTH_LONG).show(); 
     } catch (IOException e) {
      e.printStackTrace();
      Toast.makeText(MainActivity.this,
        e.toString(), Toast.LENGTH_LONG).show(); 
     }
     
     return dest; 
    }

    private String QueryYahooWeather(){
     
     String qResult = "";
     String queryString = "http://weather.yahooapis.com/forecastrss?w=2459115";
     
     HttpClient httpClient = new DefaultHttpClient();
     HttpGet httpGet = new HttpGet(queryString);
       
     try {
      HttpEntity httpEntity = httpClient.execute(httpGet).getEntity();
      
      if (httpEntity != null){
       InputStream inputStream = httpEntity.getContent();
       Reader in = new InputStreamReader(inputStream);
       BufferedReader bufferedreader = new BufferedReader(in);
       StringBuilder stringBuilder = new StringBuilder();
          
       String stringReadLine = null;

       while ((stringReadLine = bufferedreader.readLine()) != null) {
        stringBuilder.append(stringReadLine + "\n"); 
       }
          
       qResult = stringBuilder.toString(); 
      } 
     } catch (ClientProtocolException e) {
      e.printStackTrace();
      Toast.makeText(MainActivity.this,
        e.toString(), Toast.LENGTH_LONG).show(); 
     } catch (IOException e) {
      e.printStackTrace();
      Toast.makeText(MainActivity.this,
        e.toString(), Toast.LENGTH_LONG).show(); 
     }
     
     return qResult; 
    }
    
}


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
    <TextView
        android:id="@+id/status"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textStyle="bold" />
    <ScrollView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">
        <TextView
            android:id="@+id/weather"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content" />
    </ScrollView>

</LinearLayout>


download filesDownload the files.

Next:
- Retain data using Fragment API setRetainInstance()


No comments: