Thursday, March 14, 2013

Retain data using Fragment API setRetainInstance()

Last post demonstrate how to "Retaining data during orientation change, with deprecated onRetainNonConfigurationInstance() and getLastNonConfigurationInstance()". It's suggested to use the Fragment API setRetainInstance() instead.

In this example, we will implement two almost identical fragments to demonstrate setRetainInstance(). Fragment1 extends android.support.v4.app.Fragment, without calling setRetainInstance(true). It do some long time job, loading Yahoo Weather as do in last exercise, in background. Fragment2 simple extends Fragment1 and override onActivityCreated(), to call setRetainInstance(true).




Create /res/layout/fragmentlayout.xml to define layout of the fragments.
<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: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>


Create Fragment1 class extends android.support.v4.app.Fragment.
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.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;

public class Fragment1 extends Fragment {
 
 TextView weather, status;
 MyWeather weatherResult;
 
 Thread myThread;
 
 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
 public View onCreateView(LayoutInflater inflater, ViewGroup container,
   Bundle savedInstanceState) {
  View myFragmentView = inflater.inflate(R.layout.fragmentlayout, container, false);
  weather = (TextView)myFragmentView.findViewById(R.id.weather);
        status = (TextView)myFragmentView.findViewById(R.id.status);
  return myFragmentView;
 }

 @Override
 public void onActivityCreated(Bundle savedInstanceState) {
  // TODO Auto-generated method stub
  super.onActivityCreated(savedInstanceState);
  
  if(weatherResult != null){
   weather.setText(weatherResult.toString());
   status.setText("Reloaded previous weatherResult");
  }else{
   status.setText("loadYahooWeather()");
   loadYahooWeather();
  }
 }

 protected void loadYahooWeather(){

  myThread = new Thread(new Runnable(){
         
         @Override
         public void run() {
          try {
           Thread.sleep(1000); 
          } catch (InterruptedException e) {
           e.printStackTrace(); 
          }
          
          String weatherString = QueryYahooWeather();
        
          try {
           Thread.sleep(1000); 
          } catch (InterruptedException e) {
           e.printStackTrace(); 
          }
        
          Document weatherDoc = convertStringToDocument(weatherString);
              
          try {
           Thread.sleep(1000); 
          } catch (InterruptedException e) {
           e.printStackTrace(); 
          }
          
          weatherResult = parseWeather(weatherDoc);
          FragmentActivity parentActivity = getActivity();
          
          if(parentActivity != null){
           parentActivity.runOnUiThread(new Runnable(){
               
               @Override
               public void run() {
                weather.setText(weatherResult.toString());
                status.setText("Finished"); 
               }});
          }

         }});
        myThread.start();
 }

 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(getActivity().getApplicationContext(),
        e1.toString(), Toast.LENGTH_LONG).show(); 
     } catch (SAXException e) {
      e.printStackTrace();
      Toast.makeText(getActivity().getApplicationContext(),
        e.toString(), Toast.LENGTH_LONG).show(); 
     } catch (IOException e) {
      e.printStackTrace();
      Toast.makeText(getActivity().getApplicationContext(),
        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(getActivity().getApplicationContext(),
        e.toString(), Toast.LENGTH_LONG).show(); 
     } catch (IOException e) {
      e.printStackTrace();
      Toast.makeText(getActivity().getApplicationContext(),
        e.toString(), Toast.LENGTH_LONG).show(); 
     }
     
     return qResult; 
    }

}


Create Fragment2 extends Fragment1, it have only one method, override onActivityCreated() to call setRetainInstance(true).
package com.example.androidyahooweatherdom;

import android.os.Bundle;

public class Fragment2 extends Fragment1 {

 @Override
 public void onActivityCreated(Bundle savedInstanceState) {
  // TODO Auto-generated method stub
  super.onActivityCreated(savedInstanceState);
  setRetainInstance(true);
 }

}


Edit /res/layout/activity_main.xml and /res/layout-land/activity_main.xml to define the layout in normal and landscape orientation.

Remark: In order to retain the instance of the fragments, both fragments have to be assigned with ID, even your will not access it.

/res/layout/activity_main.xml
<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" >

    <fragment
        class="com.example.androidyahooweatherdom.Fragment1"
        android:id="@+id/fragment1"
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:layout_weight="1" />
    <fragment
        class="com.example.androidyahooweatherdom.Fragment2"
        android:id="@+id/fragment2"
        android:layout_width="match_parent"
        android:layout_height="0px"
        android:layout_weight="1" />

</LinearLayout>


/res/layout-land/activity_main.xml
<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="horizontal"
    tools:context=".MainActivity" >

    <fragment
        class="com.example.androidyahooweatherdom.Fragment1"
        android:id="@+id/fragment1"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1" />
    <fragment
        class="com.example.androidyahooweatherdom.Fragment2"
        android:id="@+id/fragment2"
        android:layout_width="0px"
        android:layout_height="match_parent"
        android:layout_weight="1" />

</LinearLayout>


MainActivity.java
package com.example.androidyahooweatherdom;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;

public class MainActivity extends FragmentActivity {

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

}


download filesDownload the files.


Related:
- Share IntentService among Fragments


3 comments:

Anonymous said...

If the purpose of the article is to simply explain the use of the Fragment API's setRetainInstance, why do you have to complicate the example so much when the code in the article is so badly formatted? It's so hard to read

Andrew Nyago said...

I think the code serves a higher purpose than just demonstrating Fragments.

Anonymous said...

Ok, but you aren't giving any explication, only an example...