Friday, March 22, 2013

Share IntentService among Fragments

The post "Perform background processing with IntentService" demonstrate how to use IntentService in Activity. In this exercise, we are going to modify the exercise of "Yahoo Weather" to demonstrate how to share one IntentService by three Fragments. Here one IntentService means one common IntentService class, not one common IntentService object.




Create MyIntentService.java, it's our common IntentService class, to load Yahoo Weather in background service.
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.app.IntentService;
import android.content.Intent;

public class MyIntentService extends IntentService {

String ExtraAction;
String woeid;
MyWeather weatherResult;


public static final String EXTRA_KEY_ACTION = "EXTRA_ACTION";
public static final String EXTRA_KEY_WOEID = "EXTRA_WOEID";
public static final String EXTRA_KEY_WEATHER = "EXTRA_WEATHER";

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;

}
}

public MyIntentService() {
super("com.example.androidintentservice.MyIntentService");
}

@Override
protected void onHandleIntent(Intent intent) {

//get input
ExtraAction = intent.getStringExtra(EXTRA_KEY_ACTION);
woeid = intent.getStringExtra(EXTRA_KEY_WOEID);

loadYahooWeather();

//dummy delay
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

//return result
Intent intentResponse = new Intent();
intentResponse.setAction(ExtraAction);
intentResponse.addCategory(Intent.CATEGORY_DEFAULT);
intentResponse.putExtra(EXTRA_KEY_WEATHER, weatherResult.toString());
sendBroadcast(intentResponse);
}

protected void loadYahooWeather(){

String weatherString = QueryYahooWeather();
Document weatherDoc = convertStringToDocument(weatherString);
weatherResult = parseWeather(weatherDoc);
}

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();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

return dest;
}

private String QueryYahooWeather(){

String qResult = "";
String queryString = "http://weather.yahooapis.com/forecastrss?w=" + woeid;

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();
} catch (IOException e) {
e.printStackTrace();
}

return qResult;
}

}


Modify AndroidManifest.xml to add <service> of "MyIntentService", and add permission of "android.permission.INTERNET".
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.androidyahooweatherdom"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.INTERNET"/>

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.androidyahooweatherdom.MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="MyIntentService"></service>
</application>

</manifest>


res/layout/fragmentlayout.xml, layout of our 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>


Creater a abstract class MyAbsYWeatherFragment extends Fragment, all our fragments will extend it. All it's sub-class have to override init_country() method to provide the target city of Yahoo Weather, a string of ACTION_RESPONSE for IntentFilter, and boolean retainInst to determine is it need retain instance. It will start our IntentService and handle all job for our fragments.
package com.example.androidyahooweatherdom;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
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;

public abstract class MyAbsYWeatherFragment extends Fragment {

TextView weather, status;
String stringWeatherResult;

private MyBroadcastReceiver myBroadcastReceiver;

String woeid;
String ACTION_RESPONSE;
boolean retainInst;
abstract void init_country();

public MyAbsYWeatherFragment(){
super();
init_country();
}

@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);

myBroadcastReceiver = new MyBroadcastReceiver();

//register BroadcastReceiver
IntentFilter intentFilter = new IntentFilter(ACTION_RESPONSE);
intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
getActivity().registerReceiver(myBroadcastReceiver, intentFilter);

return myFragmentView;
}

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

if(stringWeatherResult != null){
weather.setText(stringWeatherResult);
status.setText("Reloaded previous weatherResult");
}else{
status.setText("loadYahooWeather()");
startServiceToLoadYahooWeather();
}

setRetainInstance(retainInst);
}

@Override
public void onDestroyView() {
// TODO Auto-generated method stub
super.onDestroyView();
//un-register BroadcastReceiver
getActivity().unregisterReceiver(myBroadcastReceiver);
}

protected void startServiceToLoadYahooWeather(){
Intent intentMyIntentService = new Intent(getActivity().getApplicationContext(), MyIntentService.class);
intentMyIntentService.putExtra(MyIntentService.EXTRA_KEY_ACTION, ACTION_RESPONSE);
intentMyIntentService.putExtra(MyIntentService.EXTRA_KEY_WOEID, woeid);
getActivity().startService(intentMyIntentService);
}

public class MyBroadcastReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
stringWeatherResult = intent.getStringExtra(MyIntentService.EXTRA_KEY_WEATHER);

FragmentActivity parentActivity = getActivity();
if(parentActivity != null){
parentActivity.runOnUiThread(new Runnable(){

@Override
public void run() {
weather.setText(stringWeatherResult);
status.setText("Finished");
}});
}
}

}

}


Create three sub-class of MyAbsYWeatherFragment, Fragment1, Fragment2 and Fragment3. They are the actual fragments in the layout.

Fragment1.java
package com.example.androidyahooweatherdom;

public class Fragment1 extends MyAbsYWeatherFragment {

@Override
void init_country() {
//New York
woeid = "2459115";
ACTION_RESPONSE = "androidyahooweatherdom.RESPONSE.NewYork";
retainInst = false;
}

}


Fragment2.java
package com.example.androidyahooweatherdom;

public class Fragment2 extends MyAbsYWeatherFragment {

@Override
void init_country() {
//New York
woeid = "2459115";
ACTION_RESPONSE = "androidyahooweatherdom.RESPONSE.NewYork";
retainInst = true;
}

}


Fragment3.java
package com.example.androidyahooweatherdom;

public class Fragment3 extends MyAbsYWeatherFragment {

@Override
void init_country() {
//London
woeid = "23416974";
ACTION_RESPONSE = "androidyahooweatherdom.RESPONSE.London";
retainInst = true;
}

}


Modify /res/layout/activity_main.xml and /res/layout-land/activity_main.xml to add the three fragments in normal and landscape orientation.

/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" />
<fragment
class="com.example.androidyahooweatherdom.Fragment3"
android:id="@+id/fragment3"
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" />
<fragment
class="com.example.androidyahooweatherdom.Fragment3"
android:id="@+id/fragment3"
android:layout_width="0px"
android:layout_height="match_parent"
android:layout_weight="1" />

</LinearLayout>


MainActivity
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);
}

}


- Fragment1 call setRetainInstance(flase), so everytime orientation changed, it will re-start and reload new weather.

- Fragment3 call setRetainInstance(true), so it will not reload weather.

- Fragment2 call setRetainInstance(true), it will not reload weather too. But it have the same Action string for IntentFilter of Fragment1, so when IntentService of Fragment1 finished, BroadcastReceiver of Fragment2 will be called also.



download filesDownload the files.

If you have RuntimeException: Unable to instantiate activity ComponentInfo, please read HERE.

No comments:

Post a Comment