Mobile

Tutoriel : utiliser un web service SOAP sous Android

Publié le : Auteur: Abdenour BOUATELI 12 commentaires
mobile

0. Introduction

Un web service permet la communication et l’échange de données entre une application et un système via internet. SOAP (Simple Object Access Protocol) est un protocole réseau permettant de faire des appels de procédures sur une machine distante à l’aide d’un serveur d’application.

Dans ce tutoriel, nous allons voir comment utiliser un web service SOAP sous Android.
Par contre ce tutoriel n’aborde pas les sujets suivants :

  • Comment créer un web service
  • Comment tester un web service
  • Comment créer un projet Android sous ADT
  • Comment exécuter une application Android

Public concerné : Développeur Java, Architecte.

1. Pré-requis

Il faut disposer des outils ci-dessous :

  • Eclipse + ADT plugin
  • Android SDK Tools
  • Android Platform-tools

Vous pouvez les téléchargez depuis http://developer.android.com/sdk/index.html.

2. Le web service WSAgencies

Ce tutoriel suppose que le web service est déjà créé. WSAgencies retourne une liste « d’agences » Sodifrance avec leurs coordonnées. Testé avec SOAPUI4.5, le service envoie la réponse suivante :

<soapenv:Envelope xmlns:soapenv="http://www.w3.org/2003/05/soap-envelope">
   <soapenv:Body>
      <ns:getAgencesResponse xmlns:ns="http://ws.android.com">
         <ns:return><![CDATA[<Agencies><Agency> <id>0</id> <name>Rennes</name> <label>SODIFRANCE...</Agency></Agencies>]]></ns:return>
      </ns:getAgencesResponse>
   </soapenv:Body>
</soapenv:Envelope>

3. Création d’un projet Android

Ce tutoriel suppose que le projet « Sodidroid » est déjà créé. Nous allons récupérer quelques sources (layout, images) de  l’application « Sodidroid » développée par Florent Dupont, depuis le lien suivant :

https://github.com/JavaTeam/Sodidroid/tree/master/res

4. Connexion et envoi de requête avec SOAP

Pour que notre application Android « Sodidroid »  communique avec  les web services, il faut impérativement disposer de la librairie ksoap2. Pour cela, il faut télécharger le jar ksoap2-android-assembly-2.5.2-jar-with-dependencies.jar et le copier dans le répertoire /libs  du projet  Android « Sodidroid ».

Ensuite, dans le fichier AndroidManifest de Sodidroid, il faut ajouter la permission d’accès à internet :

<uses-permission android:name="android.permission.INTERNET" />

Maintenant, il faut ajouter un appel de notre web service dans notre application Sodidroid :

public class AgencySOAFetcher {

	//Call Web service
	private static final String SOAP_ACTION = "http://ws.android.com/getAgences";
	private static final String METHOD_NAME = "getAgences";
	private static final String NAMESPACE = "http://ws.android.com";
	private static final String URL = "http://169.254.96.126:8080/AndroidWSTest/services/WSAgencies?wsdl";

	private final SoapSerializationEnvelope envelope;

	public AgencySOAFetcher() {
		SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
		envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
		envelope.setOutputSoapObject(request);
		envelope.dotNet = false;
	}

	public List<Agency> Fetch() {
		HttpTransportSE httpRequest = new HttpTransportSE(URL);
		AgencySOAHandler agencyParser = new AgencySOAHandler();
		try {
			httpRequest.call(SOAP_ACTION, envelope);
			SoapPrimitive response = (SoapPrimitive) envelope.getResponse();
			Xml.parse(response.toString(), agencyParser);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return agencyParser.getAgencies();
	}
}

5. Analyser la réponse du Web service « WSAgencies »

Pour récupérer un résultat d’un web service, il suffit d’exécuter les lignes de code suivantes :

SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
...
SoapPrimitive response = (SoapPrimitive) envelope.getResponse();
System.out.println(response.toString());

L’appel du service nous renvoie une chaîne de caractères ressemblant à :

<Agencies><Agency><id>0</id><name>Rennes</name><label>SODIFRANCE (Siège Social)</label><adress>P.A.
 la Bretèche<br>CS 26804 Avenue Saint Vincent&lt;br&gt;35768 Saint Grégoire cedex</adress>
<longitude>-.684159</longitude><latitude>48.14</latitude><phone>0299234600</phone></Agency><Agency>
<id>1</id> <name>Nantes</name><label>SODIFRANCE</label><adress>P.A. la Bretèche<br>CS 26804 Avenue
Saint Vincent<br>35768 Saint Grégoire cedex</adress> <longitude>-1.53528</longitude><latitude>
47.243905</latitude><phone>0240185200</phone></Agency><Agency><id>2</id> <name>Le Mans</name>
<label>SODIFRANCE</label><adress>Novaxis 2 - Bât. B &lt;br&gt;2, rue de la Voie Lactée<br>
72000 Le Mans</adress><longitude>0.188875</longitude><latitude>47.995133</latitude><phone>
0243252500</phone></Agency></Agencies>

Que remarque-t-on ?
Le résultat renvoyé est sous forme XML. Par conséquent, afin d’analyser le résultat de retour du web service, nous allons ajouter une fonction qui va parser le résultat. Cette fonction ressemblera à ceci :

public class AgencySOAHandler extends DefaultHandler {
	private ArrayList<Agency> agencies = new ArrayList<Agency>();
	private Agency currentAgency = new Agency();
	private String currentNodeText;

	//<Agency><id><name><label><adress><longitude><latitude><phone>
	private final String AGENCY = "Agency";
	private final String ID = "id";
	private final String NAME = "name";
	private final String LABEL = "label";
	private final String PHONE = "phone";
	private final String ADRESS = "adress";
	private final String LONGITUDE = "longitude";
	private final String LATITUDE = "latitude";
	@Override
	public void startElement(String uri, String localName, String qName,
                                 Attributes attributes) throws SAXException {
		// Create a new Agency for every corresponding
		// <Stock> node in the XML document
		if (localName.equalsIgnoreCase(AGENCY)) {
			currentAgency = new Agency();
			}
		}
	@Override
	public void characters(char[] ch, int start, int length)  throws SAXException {
		// Retrieve the text content of the current node
		// that is being processed
		currentNodeText = new String(ch, start, length);
		}
	@Override
	public void endElement(String uri, String localName, String qName)  throws SAXException {

		if(localName.equalsIgnoreCase(ID)){
			currentAgency.setId(Integer.valueOf(currentNodeText));
			}else if(localName.equalsIgnoreCase(NAME)){
				currentAgency.setName(currentNodeText);
			}else if(localName.equalsIgnoreCase(LONGITUDE)){
				currentAgency.setLongitude(Double.valueOf(currentNodeText));
			}else if(localName.equalsIgnoreCase(LATITUDE)){
			currentAgency.setLatitude(Double.valueOf(currentNodeText));
			}else if(localName.equalsIgnoreCase(LABEL)){
				currentAgency.setLabel(currentNodeText);
			}else if(localName.equalsIgnoreCase(PHONE)){
				currentAgency.setPhone(currentNodeText);
			}else if(localName.equalsIgnoreCase(ADRESS)){
				currentAgency.setAdress(currentNodeText);
			}else if(localName.equalsIgnoreCase(AGENCY)){
					// When the </Stock> element is reached,
                                        // this quote object is complete.
					agencies.add(currentAgency);
					}
		}
	public ArrayList<Agency> getAgencies() {
		return agencies;
		}
}

6. Mapping réponse SOAP et Activité

L’affichage des données  renvoyées par notre web service se fait dans la classe AgencySOAListActivity :

public class AgencySOAListActivity extends ListActivity {

	private List<Agency> agencyResult;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_agency_list);

		AgencySOAFetcher sqf = new AgencySOAFetcher();
		agencyResult = sqf.Fetch();

		setListAdapter(new AgencySOAAdapter(this, agencyResult));
		registerForContextMenu(getListView());

		//return;

	}
	@Override
    protected void onListItemClick(ListView l,  View view, int position, long id) {
      super.onListItemClick(l, view, position, id);
      final Intent t = new Intent(this, AgencySOADetailActivity.class);
	     t.putExtra("_currentAgency", agencyResult.get(position));
		 startActivity(t);
    }

	@Override
	public void onCreateContextMenu(ContextMenu menu, View v,
			ContextMenuInfo menuInfo) {
		super.onCreateContextMenu(menu, v, menuInfo);
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.agency_item_menu, menu);
	}
}

La classe AgencySOAAdapter est indispensable, elle permet à l’activité AgencySOAListActivity d’orchestrer les données renvoyées par le web service :

public class AgencySOAAdapter extends ArrayAdapter<Agency> {
    private final Activity activity;
    private final List<Agency> agencies;

    public AgencySOAAdapter(Activity activity, List<Agency> objects) {
        super(activity, R.layout.activity_agency_item , objects);
        this.activity = activity;
        this.agencies = objects;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View rowView = convertView;
        AgencyView agencyView = null;

        if(rowView == null)
        {
            // Get a new instance of the row layout view
            LayoutInflater inflater = activity.getLayoutInflater();
            rowView = inflater.inflate(R.layout.activity_agency_item, null);

            // Hold the view objects in an object,
            // so they don't need to be re-fetched
            agencyView = new AgencyView();
            agencyView.titre = (TextView) rowView.findViewById(R.id.titre);
            agencyView.description = (TextView) rowView.findViewById(R.id.description);

            // Cache the view objects in the tag,
            // so they can be re-accessed later
            rowView.setTag(agencyView);
        } else {
        	agencyView = (AgencyView) rowView.getTag();
        }

        // Transfer the stock data from the data object
        // to the view objects
        Agency currentAgency = agencies.get(position);
        agencyView.titre.setText(currentAgency.getLabel());
        agencyView.description.setText(currentAgency.getName());

        return rowView;
    }

    protected static class AgencyView {
        protected TextView titre;
        protected TextView description;
    }
}

7. Conclusion

J’espère que ce tutoriel vous aura aidé à savoir utiliser SOAP depuis Android.

  • Bonjour,

    Merci beaucoup pour cette article. Cependant j’avoue être un peu surpris de devoir développer un filtre SAX pour parser le body SOAP XML et construire soit même une instance Agency.

    Je n’ai jamais développé sous Android, mais aujourd’hui quand on parle de WebService on pense souvent à JAX-WS pour gérer ses WebServices SOAP coté client ou serveur. JAX-WS simplifie considérablement le client SOAP. Il faut simplement annoter Agency avec des annotations JAXB et annoter le service client avec des annotations JAX-WS.

    Il existe plusieurs implémentations de JAX-WS, celle que j’utilise est Apache CXF (voir articles http://angelozerr.wordpress.com/about/jaxwscxf/ ).

    En cherchant rapidement android,/CXF j’ai trouvé ce lien http://code.google.com/p/android-ws-client/wiki/wsClientWiki donc je pense que c’est faisable d’utiliser JAX-WS avec android.

    Il existe aussi JAX-RS qui je trouve est beaucoup plus souple que JAX-WS (permet de transférer du JSON/Text/XML au lieu d’un message SOAP complexe, permet de faire du streaming simplement pour les upload/downlod de binaires etc). Apache CXF supporte aussi JAX-RS, donc je pense qu’il est aussi possible d’utiliser JAX-RS (service REST) avec android.

    Angelo

    • abou92

      Bonjour Angelo,

      J’ai juste utilisé le parseur SAX, qui est l’api XML
      par défaut d’Android.
      Mon but est de montrer comment utiliser web service
      sous Android.
      Evidemment, le flux XML pourrait être parsé autrement, en
      utilisant des techniques suivantes:
      >Simple XML

      http://simple.sourceforge.net/
      >JSON
      XML
      http://jsongen.byingtondesign.com/
      >JAXB
      Le JAXB de JDK6 n’est
      pas supporté par Android, mais il y’a une version JAXB versus Android
      qui est
      pas
      mal!!!
      https://github.com/plutext/AndroidDocxToHtml/tree/master/libs
      Cette
      version, je l’ai testée et elle marche très bien!!!
      Ma nouvelle classe
      AgencySOAJAXBHandler peut remplacer la classe AgencySOAHandler (SAX)
      :

      public class AgencySOAJAXBHandler extends DefaultHandler {
      private
      List agencies = new ArrayList();
      private String
      bodyXml;
      public AgencySOAJAXBHandler(String bodyXml) throws
      JAXBException{
      this.bodyXml = bodyXml;
      JAXBContext jc =
      JAXBContext.newInstance(« com.sodidroid.model »);
      Unmarshaller un =
      jc.createUnmarshaller();
      Agencies newagencies = (Agencies)un.unmarshal(new
      StringReader(bodyXml));
      agencies = newagencies.getAgency();
      }

      Quant
      au JAX-WS, pour développer un web service, c’est éfficace!
      Le client JAX-WS
      depuis Android ne marche pas, pareil, il existe une version JAX-WS versus
      Android.
      Cette version, je ne l’ai pas testée depuis
      Android

      http://code.google.com/p/android-ws-client/downloads/detail?name=AndroidWSSampleApp-1.2.0.zip

      Avec
      les jars
      suivants:

      jinouts-ws.jar
      jinouts-ws-support.jar
      soapUI.jar
      soap-xmlbeans-1.2.jar
      wsdl4j-1.6.2-fixed.jar

      On
      peut imaginer une classe AgencySOAJAXWSHandler qui peut remplacer la classe
      AgencySOAHandler (SAX) :

      @WebServiceClient(name = « WSAgencies »,

      wsdlLocation =
      « http://116.68.194.105:8080/WSAgencies?WSDL »,
      targetNamespace =
      « http://ws.android.com »)
      public class AgencySOAJAXWSHandler extends
      org.jinouts.ws.JinosService {

      public final static URL
      WSDL_LOCATION;

      public final static QName SERVICE = new
      QName(« http://ws.android.com », « WSAgencies »);
      public final static QName
      LoginPort = new QName(« http://ws.android.com », « WSAgenciesPort »);
      static
      {
      URL url = null;
      try {
      url = new
      URL(« http://116.68.194.105:8080/WSAgencies?WSDL »);
      } catch
      (MalformedURLException e)
      {
      java.util.logging.Logger.getLogger(LoginService.class.getName())
      .log(java.util.logging.Level.INFO,

      « Can not initialize the default wsdl from {0} »,
      « http://116.68.194.105:8080/WSAgencies?WSDL »);
      }
      WSDL_LOCATION =
      url;
      }

      public WSAgencies(URL wsdlLocation) {
      super(wsdlLocation,
      SERVICE);
      }

      public WSAgencies(URL wsdlLocation, QName serviceName)
      {
      super(wsdlLocation, serviceName);
      }

      public WSAgencies()
      {
      super(WSDL_LOCATION, SERVICE);
      }

      /**
      *
      * @return
      *
      returns Login
      */
      @WebEndpoint(name = « WSAgenciesPort »)
      public
      List getAgencies() {
      return super.getPort(WSAgenciesPort,
      WSAgencies.class);
      }

      }

      Cdt,

      Abdenour

      • Bonsoir Abdenour,

        Merci de ta réponse. Je me suis permis de faire ma remarque car je pense que ca peut faire peur a quelqu’un qui decouvre les webservices avecAndroid de devoir coder son propre parser SAX. Merci pour tes exemples de code. En ce qui me concerne j’utilise javax.xml.ws.Service pour creer mon client SOAP.

        J’insiste encore sur le fait que je pense que JAX-RS est beaucoup plus souple, voir plus performant (un flux JSON est plus petit qu’un flux XML SOAP).

        Angelo

  • Florent Dupont

    Super article.
    C’est bien que les sources évoluent et servent de bases pour permettre à chacun d’expérimenter.
    C’est le premier tutoriel technique sur le blog. ça enrichit encore plus le contenu.
    Merci de ton partage.

  • Aftab

    Bonjour,

    Tous grand merci pour ce tuto qui semble etre bien detailler.

    Je rencontre un petit souci : AgencySOADetailActivity.class,
    Tu fais référence a cette classe dans la classe AgencySOAListActivity, dans la methode onListItemClick.

    Le soucis c que tu en jamais dans le tutoriel de cette classe. Du coup je me dit c est une classe que je devrais surement créer moi même … ok, mais je sais pas trop quoi mettre dedans en faite, si tu saurais m éclairer ?

    Et a la ligne suivante ou tu fais sa :
    t.putExtra("_currentAgency", agencyResult.get(position));

    j obtien cette erreur de compilation :

    The method putExtra(String, boolean) in the type Intent is not applicable for the arguments (String, Agency)

    G dl la base du projet sur votre github.

    Merci davance pour votre aide

  • Aftab

    Encore moi desole, j essaye juste de comprendre la philosophie :

    Donc avec la classe AgencyGetchSoap je vais faire une demande et avoir une reponse avec des resultats qui seront stocker dans une liste.
    Mais c est a moi de creer une classe qui va faire appel au constructeur et a la methode qui retourne une liste ?

    Je me demandais sa serait possible pour toi de deposer le code source de ton projet, pour que je puisse mieux comprendre le tous ?

    Un tous grand merci 🙂

    • abou92

      Bonjour Aftab,
      Le but de ce tutoriel est justement de montrer comment appeler un web service depuis Android.
      Il n’a pas été question d’aborder le sujet « Comment créer une application Android ? ».
      Je n’ai pas encore déployé mes sources sur Github, car cette application Android sera présentée dans un prochain Labo Android chez Sodifrance, prévu fin Mars.
      Quand à ton erreur :
      « The method putExtra(String, boolean) in the type Intent is not applicable for the arguments (String, Agency) »
      il faut simplement sérialiser la classe Agency (class Agency implements Serializable)
      Par contre, je vais t’aider sur certaines sources:
      AgencySOADetailActivity:
      public class AgencySOADetailActivity extends Activity{
      protected TextView _agencyName;
      protected TextView _agencyLabel;
      protected TextView _agencyAddress;
      protected TextView _agencyPhone;
      protected ImageButton _callBtn;
      protected ImageButton _mapBtn;
      protected ImageButton _mailBtn;
      private Agency _currentAgency;
      @Override
      public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_agency_detail);
      _currentAgency = (Agency)this.getIntent().getSerializableExtra(« _currentAgency »);
      //Retrieve a variables
      _agencyName = (TextView)findViewById(R.id.detail_name);
      _agencyLabel = (TextView)findViewById(R.id.detail_label);
      _agencyAddress = (TextView)findViewById(R.id.detail_address);
      _agencyPhone = (TextView)findViewById(R.id.detail_phone);
      _callBtn = (ImageButton)findViewById(R.id.detail_call_btn);
      _mapBtn = (ImageButton)findViewById(R.id.detail_map_btn);
      _mailBtn = (ImageButton)findViewById(R.id.detail_mail_btn);
      //Update a variables
      _agencyName.setText(_currentAgency.getName());
      _agencyLabel.setText(_currentAgency.getLabel());
      _agencyAddress.setText(_currentAgency.getAdress());
      _agencyPhone.setText(_currentAgency.getPhone());

      }
      Cordialement,
      Abou92

  • Aftab

    Bonjour,

    Tous grand merci pour ce tuto qui semble etre bien detailler.

    Je rencontre un petit souci : AgencySOADetailActivity.class,

    Tu fais référence a cette classe dans la classe AgencySOAListActivity, dans la methode onListItemClick.

    Le soucis c que tu en jamais dans le tutoriel de cette classe. Du coup je me dit c est une classe que je devrais surement créer moi même … ok, mais je sais pas trop quoi mettre dedans en faite, si tu saurais m éclairer ?

    Et a la ligne suivante ou tu fais sa :

    t.putExtra("_currentAgency", agencyResult.get(position));

    j obtien cette erreur de compilation :

    The method putExtra(String, boolean) in the type Intent is not applicable for the arguments (String, Agency)

    G dl la base du projet sur votre github.

    Merci davance pour votre aide

  • Aftab

    Encore moi desole, j essaye juste de comprendre la philosophie :

    Donc avec la classe AgencyGetchSoap je vais faire une demande et avoir une reponse avec des resultats qui seront stocker dans une liste.

    Mais c est a moi de creer une classe qui va faire appel au constructeur et a la methode qui retourne une liste ?

    Je me demandais sa serait possible pour toi de deposer le code source de ton projet, pour que je puisse mieux comprendre le tous ?

    Un tous grand merci 🙂

  • abou92

    Bonjour Aftab,

    Le but de ce tutoriel est justement de montrer comment appeler un web service depuis Android.

    Il n’a pas été question d’aborder le sujet « Comment créer une application Android ? ».

    Je n’ai pas encore déployé mes sources sur Github, car cette application Android sera présentée dans un prochain Labo Android chez Sodifrance, prévu fin Mars.

    Quand à ton erreur :

    « The method putExtra(String, boolean) in the type Intent is not applicable for the arguments (String, Agency) »

    il faut simplement sérialiser la classe Agency (class Agency implements Serializable)

    Par contre, je vais t’aider sur certaines sources:

    AgencySOADetailActivity:

    public class AgencySOADetailActivity extends Activity{

    protected TextView _agencyName;

    protected TextView _agencyLabel;

    protected TextView _agencyAddress;

    protected TextView _agencyPhone;

    protected ImageButton _callBtn;

    protected ImageButton _mapBtn;

    protected ImageButton _mailBtn;

    private Agency _currentAgency;

    @Override

    public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_agency_detail);

    _currentAgency = (Agency)this.getIntent().getSerializableExtra("_currentAgency");

    //Retrieve a variables

    _agencyName = (TextView)findViewById(R.id.detail_name);

    _agencyLabel = (TextView)findViewById(R.id.detail_label);

    _agencyAddress = (TextView)findViewById(R.id.detail_address);

    _agencyPhone = (TextView)findViewById(R.id.detail_phone);

    _callBtn = (ImageButton)findViewById(R.id.detail_call_btn);

    _mapBtn = (ImageButton)findViewById(R.id.detail_map_btn);

    _mailBtn = (ImageButton)findViewById(R.id.detail_mail_btn);

    //Update a variables

    _agencyName.setText(_currentAgency.getName());

    _agencyLabel.setText(_currentAgency.getLabel());

    _agencyAddress.setText(_currentAgency.getAdress());

    _agencyPhone.setText(_currentAgency.getPhone());

    ...

    }

    Cordialement,

    Abou92

    • Aftab

      Merci bien 🙂

  • Nassima Zouhdi

    Bonjour est ce que vous pouvez partager le projet en entier ? Et merci d’avance.