Skip to content

3. Vaka Çalışması - Randevu Yönetimi

3.1. Proje

[Tutoriel AngularJS / Spring 4] belgesinde, doktor randevularını yönetmek için bir istemci/sunucu uygulaması geliştirilmiştir. Bundan sonra bu belgeye [rdvmedecins-angular] olarak atıfta bulunacağız. Uygulamanın iki tür istemcisi vardı:

  • HTML / CSS / JS istemcisi;
  • bir Android istemcisi;

Android istemcisi, HTML sürümündeki istemciden [Cordova] aracı kullanılarak otomatik olarak elde ediliyordu. Bu bölümdeki projemiz, önceki bölümlerde edindiğimiz bilgileri kullanarak bu Android istemcisini manuel olarak yeniden oluşturmak olacaktır.

İki çözüm arasında önemli bir farka dikkat çekmek gerekir:

  • oluşturacağımız istemci yalnızca Android tabletlerde kullanılabilecek;
  • [rdvmedecins-angular] sürümünde ise mobil web istemcisi (HTML / CSS / JS) herhangi bir platformda (Android, IoS, Windows) kullanılabilir;

3.2. Android istemcisinin görünümleri

Dört farklı görünüm bulunmaktadır.

Yapılandırma görünümü

Image

Doktor seçimi ve randevu tarihi ekranı

Image

Randevu saat aralığı seçimi ekranı

Image

Randevu için müşteri seçimi ekranı

Image

3.3. Projenin mimarisi

Bu belgedeki [Exemple-15] örneğindeki (bkz. paragraf 1.16) ile benzer bir istemci/sunucu mimarisi kullanılacaktır:

Image

İstemci ile sunucu arasındaki asenkron veri alışverişi, RxAndroid kütüphanesi ile yönetilecektir.

3.4. Veritabanı

Bu belgede temel bir rol oynamamaktadır. Bilgi amaçlı olarak verilmektedir. Adı [dbrdvmedecins] olacaktır. Bu, dört tablo içeren bir MySQL5 veritabanıdır:

  

3.4.1. [MEDECINS] tablosu

Bu tablo, [RdvMedecins] uygulaması tarafından yönetilen doktorlar hakkında bilgiler içerir.

  • ID: doktoru tanımlayan numara – tablonun birincil anahtarı
  • VERSION: Tablodaki satırın sürümünü tanımlayan numara. Bu sayı, satırda her değişiklik yapıldığında 1 artırılır.
  • NOM: doktorun soyadı
  • PRENOM: doktorun adı
  • TITRE: unvanı (Bayan, Bayan, Bay)

3.4.2. [CLIENTS] tablosu

Farklı doktorların hastaları [CLIENTS] tablosunda kaydedilir:

  • ID: müşteriyi tanımlayan numara - tablonun birincil anahtarı
  • VERSION: Tablodaki satırın sürümünü tanımlayan numara. Bu sayı, satırda her değişiklik yapıldığında 1 artırılır.
  • NOM: müşterinin adı
  • PRENOM: müşterinin adı
  • TITRE: unvanı (Bayan, Bayan, Bay)

3.4.3. [CRENEAUX] tablosu

Bu tablo, RV'lerin mümkün olduğu zaman aralıklarını listeler:

  • ID: zaman aralığını tanımlayan numara - tablonun birincil anahtarı (satır 8)
  • VERSION: Tablodaki satırın sürümünü tanımlayan numara. Bu sayı, satırda her değişiklik yapıldığında 1 artırılır.
  • ID_MEDECIN: Bu zaman diliminin ait olduğu doktoru tanımlayan numara – MEDECINS (ID) sütunundaki yabancı anahtar.
  • HDEBUT: zaman aralığının başlangıç saati
  • MDEBUT: zaman aralığının başlangıç dakikası
  • HFIN: zaman aralığının bitiş saati
  • MFIN: zaman aralığının bitiş dakikası

[CRENEAUX] tablosunun ikinci satırı (yukarıdaki [1]'e bakınız), örneğin, 2 numaralı zaman diliminin saat 08:20'de başlayıp 08:40'ta bittiğini ve 1 numaralı doktora ait olduğunu gösterir (Bayan Marie PELISSIER) ait olduğunu gösterir.

3.4.4. [RV] tablosu

Bu tablo, her doktor için ayrılan RV'leri listeler:

  • ID: RV'i benzersiz bir şekilde tanımlayan numara – birincil anahtar
  • JOUR: RV'in günü
  • ID_CRENEAU: RV'in zaman aralığı – [CRENEAUX] tablosundaki [ID] alanına ilişkin yabancı anahtar – hem zaman aralığını hem de ilgili doktoru belirler.
  • ID_CLIENT: Rezervasyonun yapıldığı müşterinin numarası – [CLIENTS] tablosundaki [ID] alanında yabancı anahtar

Bu tablo, birleştirilen sütunların değerleri (JOUR, ID_CRENEAU) üzerinde tekillik kısıtlaması içeren bir 'e sahiptir:

ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (JOUR, ID_CRENEAU);

[RV] tablosundaki bir satırın değeri (JOUR1, ID_CRENEAU1) değerine sahipse, bu değer başka hiçbir yerde bulunamaz. Aksi takdirde, bu durum aynı hekim için aynı anda iki adet RV'in alınmış olduğu anlamına gelir. Java programlama açısından bakıldığında, veritabanındaki JDBC sürücüsü, bu durum meydana geldiğinde bir SQLException başlatır.

id satırındaki 3 değeri (yukarıdaki [1]'e bakınız), 23/08/2006 tarihinde 20 numaralı zaman dilimi ve 4 numaralı müşteri için bir RV'in rezerve edildiği anlamına gelir. [CRENEAUX] tablosundan, 20 numaralı randevu aralığının 16:20 - 16:40 saat aralığına karşılık geldiği ve 1 numaralı hekime (Bayan Marie PELISSIER) ait olduğu anlaşılmaktadır. [CLIENTS] tablosundan, 4 numaralı müşterinin Bayan Brigitte BISTROU olduğu anlaşılmaktadır.

3.4.5. Veritabanının Oluşturulması

Tabloları oluşturmak ve doldurmak için, |ICI| örnek arşivinde bulunan [dbrdvmedecins.sql] komut dosyasını kullanabilirsiniz.

  

[WampServer] komut dosyası (bkz. paragraf 6.15) ile şu şekilde ilerleyebilirsiniz:

 
  • [1]'te, [WampServer] simgesine tıklanır ve [PhpMyAdmin] [2] seçeneği seçilir,
  • [3]'te, açılan pencerede [Bases de données] bağlantısını seçin,
 
  • [4-6]'e, bir SQL dosyasını içe aktarın,
  • [7]'te, SQL komut dosyasını seçip [8]'te çalıştırdığımızda,
  • [9]'te veritabanı tabloları oluşturulmuştur. Bağlantılardan birini takip ediyoruz,
 
  • [10]'te tablonun içeriği görüntülenir.

Bundan sonra bu veritabanına tekrar dönmeyeceğiz, ancak okuyucunun, özellikle uygulama çalışmadığında, testler boyunca veritabanındaki gelişmeleri takip etmesi önerilir.

3.5. Web sunucusu / jSON

Image

Burada [1] sunucusuyla ilgileniyoruz. Bunu ayrıntılı olarak ele almayacağız. Bu sunucu, [Spring MVC et Thymeleaf par l'exemple] belgesinde ayrıntılı olarak açıklanmıştır. İlgilenen okuyucular bu belgeye başvurabilirler. Bu sunucu, Örnek 15'teki sunucu gibi geliştirilmiştir. Kaynak kodu örneklerde verilmiştir. Burada bu sunucunun ikili dosyasını kullanacağız:

  
  • [rdvmedecins-server-all-1.0.jar], sunucunun ikili dosyasıdır;

3.5.1. Uygulama

Bir komut penceresinde, sunucunun ikili dosyasının bulunduğu klasöre gidin:


...\rdvmedecins>dir
 Le volume dans le lecteur D s’appelle Données
 Le numéro de série du volume est 7A34-AE5F

 Répertoire de D:\data\istia-1516\projets\dvp-android-studio\rdvmedecins

09/06/2016  10:50    <DIR>          .
09/06/2016  10:50    <DIR>          ..
06/07/2014  16:36             7 631 dbrdvmedecins.sql
08/06/2016  16:31    <DIR>          rdvmedecins-client
08/06/2016  16:22    <DIR>          rdvmedecins-server
08/06/2016  16:23        29 618 709 rdvmedecins-server-all-1.0.jar

ardından sunucuyu başlatmak için aşağıdaki komutu girin (SGBD ve MySQL'in önceden başlatılmış olması gerekir):


...\rdvmedecins>java -jar rdvmedecins-server-all-1.0.jar

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                  (v1.0)

10:55:48.617 [main] INFO  rdvmedecins.boot.Boot - Starting Boot v1.0 on st-PC (D:\data\istia-1516\projets\dvp-android-studio\rdvmedecins\rdvmedecins-server-all-1.0.jar started by st in D:\data\istia-1516\projets\dvp-android-studio\rdvmedecins)
10:55:48.621 [main] INFO  rdvmedecins.boot.Boot - No active profile set, falling back to default profiles: default
10:55:48.662 [main] INFO  o.s.b.c.e.AnnotationConfigEmbeddedWebApplicationContext - Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@7085bdee: startup date [Thu Jun 09 10:55:48 CEST 2016]; root of context hierarchy
10:55:49.948 [main] INFO  o.s.b.c.e.t.TomcatEmbeddedServletContainer - Tomcat initialized with port(s): 8080 (http)
juin 09, 2016 10:55:50 AM org.apache.catalina.core.StandardService startInternal
INFOS: Starting service Tomcat
juin 09, 2016 10:55:50 AM org.apache.catalina.core.StandardEngine startInternal
INFOS: Starting Servlet Engine: Apache Tomcat/8.0.33
juin 09, 2016 10:55:50 AM org.apache.catalina.core.ApplicationContext log
INFOS: Initializing Spring embedded WebApplicationContext
10:55:50.255 [localhost-startStop-1] INFO  o.s.web.context.ContextLoader - Root
WebApplicationContext: initialization completed in 1596 ms
...
10:55:55.765 [localhost-startStop-1] INFO  o.s.s.web.DefaultSecurityFilterChain
- Creating filter chain: ...]
10:55:55.785 [localhost-startStop-1] INFO  o.s.b.c.e.ServletRegistrationBean - Mapping servlet: 'dispatcherServlet' to [/*]
10:55:55.791 [localhost-startStop-1] INFO  o.s.b.c.e.FilterRegistrationBean - Mapping filter: 'springSecurityFilterChain' to: [/*]
...
10:55:56.249 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllCreneaux/{idMedecin}],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public java.lang.String rdvmedecins.controllers.RdvMedecinsController.getAllCreneaux(long,javax.servlet.http.HttpServletResponse,java.lang.String)
throws com.fasterxml.jackson.core.JsonProcessingException
10:55:56.252 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getRvMedecinJour/{idMedecin}/{jour}],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public java.lang.String rdvmedecins.controllers.RdvMedecinsController.getRvMedecinJour(long,java.lang.String,javax.servlet.http.HttpServletResponse,java.lang.String) throws com.fasterxml.jackson.core.JsonProcessingException
10:55:56.255 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getCreneauById/{id}],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public java.lang.String rdvmedecins.controllers.RdvMedecinsController.getCreneauById(long,javax.servlet.http.HttpServletResponse,java.lang.String) throws
com.fasterxml.jackson.core.JsonProcessingException
10:55:56.257 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/ajouterRv],methods=[POST],consumes=[application/json;charset=UTF-8],produces=[application/json;charset=UTF-8]}" onto public java.lang.String rdvmedecins.controllers.RdvMedecinsController.ajouterRv(rdvmedecins.models.PostAjouterRv,javax.servlet.http.HttpServletResponse,java.lang.String) throws com.fasterxml.jackson.core.JsonProcessingException
10:55:56.259 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllClients],methods=[GET],produces=[application/json;charset=UTF-8]}" onto
public java.lang.String rdvmedecins.controllers.RdvMedecinsController.getAllClients(javax.servlet.http.HttpServletResponse,java.lang.String) throws com.fasterxml.jackson.core.JsonProcessingException
10:55:56.261 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getClientById/{id}],methods=[GET],produces=[application/json;charset=UTF-8]}"
onto public java.lang.String rdvmedecins.controllers.RdvMedecinsController.getClientById(long,javax.servlet.http.HttpServletResponse,java.lang.String) throws com.fasterxml.jackson.core.JsonProcessingException
10:55:56.264 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getMedecinById/{id}],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public java.lang.String rdvmedecins.controllers.RdvMedecinsController.getMedecinById(long,javax.servlet.http.HttpServletResponse,java.lang.String) throws com.fasterxml.jackson.core.JsonProcessingException
10:55:56.266 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getRvById/{id}],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public java.lang.String rdvmedecins.controllers.RdvMedecinsController.getRvById(long,javax.servlet.http.HttpServletResponse,java.lang.String) throws com.fasterxml.jackson.core.JsonProcessingException
10:55:56.268 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAllMedecins],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public java.lang.String rdvmedecins.controllers.RdvMedecinsController.getAllMedecins(javax.servlet.http.HttpServletResponse,java.lang.String) throws com.fasterxml.jackson.core.JsonProcessingException
10:55:56.270 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/supprimerRv],methods=[POST],consumes=[application/json;charset=UTF-8],produces=[application/json;charset=UTF-8]}" onto public java.lang.String rdvmedecins.controllers.RdvMedecinsController.supprimerRv(rdvmedecins.models.PostSupprimerRv,javax.servlet.http.HttpServletResponse,java.lang.String) throws com.fasterxml.jackson.core.JsonProcessingException
10:55:56.273 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/authenticate],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public java.lang.String rdvmedecins.controllers.RdvMedecinsController.authenticate(javax.servlet.http.HttpServletResponse,java.lang.String) throws com.fasterxml.jackson.core.JsonProcessingException
10:55:56.276 [main] INFO  o.s.w.s.m.m.a.RequestMappingHandlerMapping - Mapped "{[/getAgendaMedecinJour/{idMedecin}/{jour}],methods=[GET],produces=[application/json;charset=UTF-8]}" onto public java.lang.String rdvmedecins.controllers.RdvMedecinsController.getAgendaMedecinJour(long,java.lang.String,javax.servlet.http.HttpServletResponse,java.lang.String) throws com.fasterxml.jackson.core.JsonProcessingException
...
10:55:56.681 [main] INFO  o.s.b.c.e.t.TomcatEmbeddedServletContainer - Tomcat started on port(s): 8080 (http)
10:55:56.686 [main] INFO  rdvmedecins.boot.Boot - Started Boot in 8.231 seconds

Sunucu çok sayıda günlük kaydı görüntüler. Yukarıda, konuyu anlamaya yardımcı olacak olanları seçtik:

  • 14-18. satırlar: Makinenin 8080 numaralı bağlantı noktasında gömülü bir Tomcat sunucusu başlatılıyor. Randevu yönetimi web uygulamasını çalıştıran bu sunucudur. Bu uygulama aslında bir web hizmeti / jSON'tir: URL aracılığıyla sorgulanır ve bir jSON dizesi göndererek yanıt verir;
  • 24. satır: Web hizmeti, [Spring Security] çerçevesi ile güvenli hale getirilmiştir. Web hizmetinin URL'lerine kimlik doğrulaması yapılarak erişilir;
  • 29-44. satırlar: web hizmeti tarafından sunulan URL'ler;

Şimdi bunları ayrıntılı olarak inceleyeceğiz.

3.5.2. Web hizmetinin güvenliği

Web hizmeti tarafından sunulan URL'ler güvenli hale getirilmiştir. Sunucu, istemciden gelen HTTP isteğinde aşağıdaki başlığı bekler:

Authorization: Basic code

Beklenen kod, 'kullanıcı:şifre' dizesinin base64 ile kodlanmış hali olan [http://fr.wikipedia.org/wiki/Base64]'tir. Web hizmeti, başlangıç durumunda yalnızca 'admin' kullanıcı adını ve 'admin' şifresini kabul eder. Yukarıdaki başlık, bu belirli kullanıcı için şu satıra dönüşür:

Authorization: Basic YWRtaW46YWRtaW4=

Bu HTTP başlığını gönderebilmek için, Chrome tarayıcısının bir eklentisi olan HTTP [Advanced Rest Client] istemcisini kullanıyoruz (bkz. paragraf 6.13). Aşağıdakileri anlamak amacıyla, web hizmeti tarafından sunulan farklı URL'leri manuel olarak test edeceğiz:

  • URL tarafından beklenen parametreler;
  • yanıtının tam niteliği;

3.5.3. Doktor listesi

URL [/getAllMedecins], doktor listesini almayı sağlar:

  • [1]'te, sorgulanan URL;
  • [2]'te, bu sorgu için kullanılan HTTP yöntemi;
  • [3]'e, kullanıcının (admin, admin) güvenlik başlığı HTTP;
  • [4]'te, HTTP isteği gönderilir;

Sunucunun yanıtı şöyledir:

  • [5]'te, sunucudan gelen jSON yanıtı biçimlendirilmiş olarak;
  • [6] olarak, aynı yanıtın işlenmemiş hali;

[5] biçimi, yanıtın yapısını daha iyi görmeyi sağlar. Web hizmetinin tüm yanıtları, aşağıdaki [Response] sınıfının bir örneğidir:


package rdvmedecins.android.dao.service;

import java.util.List;

public class Response<T> {

    // ----------------- özellikler
    // işlem durumu
    private int status;
    // olası hata mesajları
    private List<String> messages;
    // yanıtın gövdesi
    private T body;

    // yapıcılar
    public Response() {

    }

    public Response(int status, List<String> messages, T body) {
        this.status = status;
        this.messages = messages;
        this.body = body;
    }

    // getter ve setter'lar
...
}
  • 9. satır: yanıtın durumu. 0 değeri hata olmadığı anlamına gelir, aksi takdirde hata oluşmuştur;
  • 11. satır: hata oluşmuşsa hata mesajlarının listesi;
  • 13. satır: müşterinin aslında beklediği yanıt;

URL [/getAllMedecins]'e verilen yanıt, jSON dizesidir ve [Response<List<Medecin>>] türünde bir nesneye aittir. [Medecin] sınıfı şu şekildedir:


package rdvmedecins.android.dao.entities;

public class Medecin extends Personne {

    // varsayılan oluşturucu
    public Medecin() {
    }

    // parametreli oluşturucu
    public Medecin(String titre, String nom, String prenom) {
        super(titre, nom, prenom);
    }

    public String toString() {
        return String.format("Medecin[%s]", super.toString());
    }

}
  1. satırda, [Medecin] sınıfı, aşağıdaki [Personne] sınıfını genişletir:

package rdvmedecins.android.dao.entities;

public class Personne extends AbstractEntity {
    // bir kişinin özellikleri
    private String titre;
    private String nom;
    private String prenom;

    // varsayılan oluşturucu
    public Personne() {
    }

    // parametreli oluşturucu
    public Personne(String titre, String nom, String prenom) {
        this.titre = titre;
        this.nom = nom;
        this.prenom = prenom;
    }

    // toString
    public String toString() {
        return String.format("Personne[%s, %s, %s, %s, %s]", id, version, titre, nom, prenom);
    }

    // alıcı ve ayarlayıcılar
    ...
}

3. satırda, [Personne] sınıfı, aşağıdaki [AbstractEntity] sınıfını genişletir:


package rdvmedecins.android.dao.entities;

import java.io.Serializable;

public class AbstractEntity implements Serializable {

    private static final long serialVersionUID = 1L;
    protected Long id;
    protected Long version;

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    // başlatma
    public AbstractEntity build(Long id, Long version) {
        this.id = id;
        this.version = version;
        return this;
    }

    @Override
    public boolean equals(Object entity) {
        String class1 = this.getClass().getName();
        String class2 = entity.getClass().getName();
        if (!class2.equals(class1)) {
            return false;
        }
        AbstractEntity other = (AbstractEntity) entity;
        return this.id == other.id;
    }

    // alıcı ve ayarlayıcılar
    ...
}

Sonuç olarak, bir [Medecin] nesnesinin yapısı şu şekildedir:


[Long id; Long version; String titre; String nom; String prenom;]

ve [Response<List<Medecin>>] nesnesinin yapısı ise şöyledir:

[int status; List<String> messages; List<Medecin> medecins]

Bundan sonra, sunucunun yanıtını tanımlamak için bu kısaltılmış tanımları kullanacağız. Ayrıca, bir süreliğine ekran görüntüsü göstermeyeceğiz. Az önce gördüklerimizi tekrarlamak yeterli olacaktır. POST sorgusu yapmamız gerektiğinde ekran görüntülerine geri döneceğiz. Ayrıca, aşağıdaki biçimde bir yürütme örneği de sunacağız:

URL

/getAllMedecins
Réponse
{"status":0,"messages":null,"medecins":
[{"id":1,"version":1,"titre":"Mme","nom":"PELISSIER","prenom":"Marie"},
{"id":2,"version":1,"titre":"Mr","nom":"BROMARD","prenom":"Jacques"},
{"id":3,"version":1,"titre":"Mr","nom":"JANDOT","prenom":"Philippe"},
{"id":4,"version":1,"titre":"Melle","nom":"JACQUEMOT","prenom":"Justine"}]}

3.5.4. Müşteri listesi

URL

/getAllClients
Réponse

Response<List<Client>> :[int status; List<String> messages;
 List<Client> clients]
Client : [Long id;  Long version; String titre;
 String nom; String prenom;]

Örnek:

URL

/getAllClients
Réponse
{"status":0,"messages":null,"clients":
[{"id":1,"version":1,"titre":"Mr","nom":"MARTIN","prenom":"Jules"},
{"id":2,"version":1,"titre":"Mme","nom":"GERMAN","prenom":"Christine"},
{"id":3,"version":1,"titre":"Mr","nom":"JACQUARD","prenom":"Jules"},
{"id":4,"version":1,"titre":"Melle","nom":"BISTROU","prenom":"Brigitte"}]}

3.5.5. Bir doktorun randevu saatleri listesi

URL
/getAllCreneaux/{idMedecin}
Réponse

Response<List<Creneau>>:[int status ; List<String> messages ;
 List<Creneau> creneaux]
Creneau : [int hdebut ; int mdebut ; int hfin ; int mfin ;]
  • [idMedecin]: randevu saatlerini öğrenmek istediğimiz hekimin kimlik numarası;
  • [hdebut]: muayenenin başlangıç saati;
  • [mdebut]: muayenenin başlangıç dakikası;
  • [hfin]: muayenenin bitiş saati;
  • [mfin]: muayenenin bittiği dakika;

10:20 ile 10:40 arasındaki bir zaman aralığı için [hdebut, mdebut, hfin, mfin]=[10, 20, 10, 40] olacaktır.

Örnek:

URL
/getAllCreneaux/1
Réponse
{"status":0,"messages":null,"creneaux":
[{"id":1,"version":1,"hdebut":8,"mdebut":0,"hfin":8,"mfin":20,"idMedecin":1},
{"id":2,"version":1,"hdebut":8,"mdebut":20,"hfin":8,"mfin":40,"idMedecin":1},
{"id":3,"version":1,"hdebut":8,"mdebut":40,"hfin":9,"mfin":0,"idMedecin":1},
{"id":4,"version":1,"hdebut":9,"mdebut":0,"hfin":9,"mfin":20,"idMedecin":1},
{"id":5,"version":1,"hdebut":9,"mdebut":20,"hfin":9,"mfin":40,"idMedecin":1},
{"id":6,"version":1,"hdebut":9,"mdebut":40,"hfin":10,"mfin":0,"idMedecin":1},
{"id":7,"version":1,"hdebut":10,"mdebut":0,"hfin":10,"mfin":20,"idMedecin":1},
{"id":8,"version":1,"hdebut":10,"mdebut":20,"hfin":10,"mfin":40,"idMedecin":1},
{"id":9,"version":1,"hdebut":10,"mdebut":40,"hfin":11,"mfin":0,"idMedecin":1},
{"id":10,"version":1,"hdebut":11,"mdebut":0,"hfin":11,"mfin":20,"idMedecin":1},
{"id":11,"version":1,"hdebut":11,"mdebut":20,"hfin":11,"mfin":40,"idMedecin":1},
{"id":12,"version":1,"hdebut":11,"mdebut":40,"hfin":12,"mfin":0,"idMedecin":1},
{"id":13,"version":1,"hdebut":14,"mdebut":0,"hfin":14,"mfin":20,"idMedecin":1},
{"id":14,"version":1,"hdebut":14,"mdebut":20,"hfin":14,"mfin":40,"idMedecin":1},
{"id":15,"version":1,"hdebut":14,"mdebut":40,"hfin":15,"mfin":0,"idMedecin":1},
{"id":16,"version":1,"hdebut":15,"mdebut":0,"hfin":15,"mfin":20,"idMedecin":1},
{"id":17,"version":1,"hdebut":15,"mdebut":20,"hfin":15,"mfin":40,"idMedecin":1},
{"id":18,"version":1,"hdebut":15,"mdebut":40,"hfin":16,"mfin":0,"idMedecin":1},
{"id":19,"version":1,"hdebut":16,"mdebut":0,"hfin":16,"mfin":20,"idMedecin":1},
{"id":20,"version":1,"hdebut":16,"mdebut":20,"hfin":16,"mfin":40,"idMedecin":1},
{"id":21,"version":1,"hdebut":16,"mdebut":40,"hfin":17,"mfin":0,"idMedecin":1},
{"id":22,"version":1,"hdebut":17,"mdebut":0,"hfin":17,"mfin":20,"idMedecin":1},
{"id":23,"version":1,"hdebut":17,"mdebut":20,"hfin":17,"mfin":40,"idMedecin":1},
{"id":24,"version":1,"hdebut":17,"mdebut":40,"hfin":18,"mfin":0,"idMedecin":1}]}

3.5.6. Bir doktorun randevu listesi

URL
/getRvMedecinJour/{idMedecin}/{jour}
Réponse

Response<List<Rv>>:[int status ; List<String> messages ;
 List<Rv> rvs]
Rv : [Date jour ; Client client ; Creneau creneau ;
 long idClient ; long idCreneau]
  • [idMedecin]: randevularını almak istediğiniz hekimin kimlik numarası;
  • URL [jour]: randevuların günü 'yyyy-aa-gg' biçiminde;
  • Yanıt [jour]: aynı, ancak Java tarih biçimi olarak;
  • [client]: randevunun müşterisi. Yapısı daha önce açıklanmıştır;
  • [idClient]: müşterinin kimlik numarası;
  • [creneau]: randevu aralığı. Yapısı daha önce açıklanmıştır;
  • [idCreneau]: zaman aralığının kimliği;

Örnek:

URL
/getRvMedecinJour/1/2014-07-08
Réponse
{"status":0,"messages":null,
"rvs":[{"id":45,"version":0,"jour":"2014-07-08","client":
{"id":1,"version":1,"titre":"Mr","nom":"MARTIN","prenom":"Jules"},"creneau":
{"id":1,"version":1,"hdebut":8,"mdebut":0,"hfin":8,"mfin":20,"idMedecin":1},
"idClient":1,"idCreneau":1}]}

3.5.7. Bir doktorun randevu takvimi

URL
/getAgendaMedecinJour/{idMedecin}/{jour}
Réponse

Response<AgendaMedecinJour>:[int status ; List<String> messages ;
 AgendaMedecinJour agenda]
AgendaMedecinJour : [Medecin medecin ;Date jour ; 
CreneauMedecinJour[] creneauxMedecinJour]
CreneauMedecinJour : [Creneau creneau ; Rv rv]
  • [idMedecin]: randevularını almak istediğiniz doktorun kimlik numarası;
  • URL [jour]: randevuların tarihi 'yyyy-aa-gg' biçiminde;
  • [agenda]: doktorun randevu takvimi;
  • [medecin]: İlgili doktor. Yapısı daha önce tanımlanmıştır;
  • Yanıt [jour]: takvim gününün Java tarih biçimi;
  • [creneauxMedecinJour]: [CreneauMedecinJour] türünde öğelerden oluşan bir dizi;
  • [creneau]: bir zaman aralığı. Yapısı daha önce açıklanmıştır;
  • [rv]: bir randevu. Yapısı daha önce açıklanmıştır;

Örnek:

URL
/getAgendaMedecinJour/1/2014-07-08
Réponse

{"status":0,"messages":null,"agenda":{"medecin":
{"id":1,"version":1,"titre":"Mme","nom":"PELISSIER","prenom":"Marie"},
"jour":1404770400000,"creneauxMedecinJour":[{"creneau":
{"id":1,"version":1,"hdebut":8,"mdebut":0,"hfin":8,"mfin":20,"idMedecin":1},
"rv":{"id":45,"version":0,"jour":"2014-07-08","client":
{"id":1,"version":1,"titre":"Mr","nom":"MARTIN","prenom":"Jules"},
"creneau":{"id":1,"version":1,"hdebut":8,"mdebut":0,"hfin":8,"mfin":20,"idMedecin":1},
"idClient":1,"idCreneau":1}},{"creneau":
{"id":2,"version":1,"hdebut":8,"mdebut":20,"hfin":8,"mfin":40,"idMedecin":1},
"rv":null},{"creneau":{"id":3,"version":1,"hdebut":8,"mdebut":40,"hfin":9,"mfin":0,"idMedecin":1},
"rv":null},{"creneau":{"id":4,"version":1,"hdebut":9,"mdebut":0,"hfin":9,"mfin":20,"idMedecin":1},
"rv":null},{"creneau":{"id":5,"version":1,"hdebut":9,"mdebut":20,"hfin":9,"mfin":40,"idMedecin":1},
"rv":null},{"creneau":{"id":6,"version":1,"hdebut":9,"mdebut":40,"hfin":10,"mfin":0,"idMedecin":1},
"rv":null},{"creneau":{"id":7,"version":1,"hdebut":10,"mdebut":0,"hfin":10,"mfin":20,"idMedecin":1},
"rv":null},{"creneau":{"id":8,"version":1,"hdebut":10,"mdebut":20,"hfin":10,"mfin":40,"idMedecin":1},
"rv":null},{"creneau":{"id":9,"version":1,"hdebut":10,"mdebut":40,"hfin":11,"mfin":0,"idMedecin":1},
"rv":null},{"creneau":{"id":10,"version":1,"hdebut":11,"mdebut":0,"hfin":11,"mfin":20,"idMedecin":1},
"rv":null},{"creneau":{"id":11,"version":1,"hdebut":11,"mdebut":20,"hfin":11,"mfin":40,"idMedecin":1},
"rv":null},{"creneau":{"id":12,"version":1,"hdebut":11,"mdebut":40,"hfin":12,"mfin":0,"idMedecin":1},
"rv":null},{"creneau":{"id":13,"version":1,"hdebut":14,"mdebut":0,"hfin":14,"mfin":20,"idMedecin":1},
"rv":null},{"creneau":{"id":14,"version":1,"hdebut":14,"mdebut":20,"hfin":14,"mfin":40,"idMedecin":1},
"rv":null},{"creneau":{"id":15,"version":1,"hdebut":14,"mdebut":40,"hfin":15,"mfin":0,"idMedecin":1},
"rv":null},{"creneau":{"id":16,"version":1,"hdebut":15,"mdebut":0,"hfin":15,"mfin":20,"idMedecin":1},
"rv":null},{"creneau":{"id":17,"version":1,"hdebut":15,"mdebut":20,"hfin":15,"mfin":40,"idMedecin":1},
"rv":null},{"creneau":
{"id":18,"version":1,"hdebut":15,"mdebut":40,"hfin":16,"mfin":0,"idMedecin":1},
"rv":null},{"creneau":{"id":19,"version":1,"hdebut":16,"mdebut":0,"hfin":16,"mfin":20,"idMedecin":1},
"rv":null},{"creneau":{"id":20,"version":1,"hdebut":16,"mdebut":20,"hfin":16,"mfin":40,"idMedecin":1},
"rv":null},{"creneau":{"id":21,"version":1,"hdebut":16,"mdebut":40,"hfin":17,"mfin":0,"idMedecin":1},
"rv":null},{"creneau":{"id":22,"version":1,"hdebut":17,"mdebut":0,"hfin":17,"mfin":20,"idMedecin":1},
"rv":null},{"creneau":
{"id":23,"version":1,"hdebut":17,"mdebut":20,"hfin":17,"mfin":40,"idMedecin":1},
"rv":null},{"creneau":
{"id":24,"version":1,"hdebut":17,"mdebut":40,"hfin":18,"mfin":0,"idMedecin":1},
"rv":null}]}}

Söz konusu zaman aralığında randevu olup olmadığı durumları ayrı ayrı ele alınmıştır.

3.5.8. Kullanıcı kimliği ile doktor bulma

URL
/getMedecinById/{idMedecin}
Réponse

Response<Medecin> :[int status ; List<String> messages ; Medecin medecin]
  • [idMedecin]: doktorun kimlik numarası;

Örnek 1:

URL
/getMedecinById/1
Réponse
{"status":0,"messages":null,"medecin":
{"id":1,"version":1,"titre":"Mme",
"nom":"PELISSIER","prenom":"Marie"}}

Örnek 2:

URL
/getMedecinById/100
Réponse
{"status":2,
"messages":["Médecin [100] inexistant"],"medecin":null}

3.5.9. Kullanıcı kimliği ile müşteri bilgilerini alma

URL
/getClientById/{idClient}
Réponse

Response<Client> :[int status ; List<String> messages ;
 Client client]
  • [idClient]: müşteri kimlik numarası;

Örnek 1:

URL
/getClientById/1
Réponse
{"status":0,"messages":null,"client":{"id":1,"version":1,"titre":"Mr","nom":"MARTIN","prenom":"Jules"}}

Örnek 2:

URL
/getClientById/100
Réponse
{"status":2,"messages":["Client [100] inexistant"],"client":null}

3.5.10. Kimlik numarasına göre bir zaman aralığı bulma

URL
/getCreneauById/{idCreneau}
Réponse

Response<Creneau> :[int status ; List<String> messages ; Creneau creneau]
  • [idCreneau]: zaman aralığının kimlik numarası;

Örnek 1:

URL
/getCreneauById/10
Réponse
{"status":0,"messages":null,"creneau":
{"id":10,"version":1,"hdebut":11,"mdebut":0,
"hfin":11,"mfin":20,"idMedecin":1}}

Cevapta, randevu saatinin sahibi olan doktorun adı yer almıyor, sadece kimlik numarası bulunuyor.

Örnek 2:

URL
/getCreneauById/100
Réponse
{"status":2,"messages":["Créneau [100] inexistant"],
"creneau":null}

3.5.11. Kullanıcı kimliği ile randevu alma

URL
/getRvById/{idRv}
Réponse

Response<Rv> :[int status ; List<String> messages ; Rv rv]
  • [idRv]: randevu kimlik numarası;

Örnek 1:

URL
/getRvById/45
Réponse
{"status":0,"messages":null,"rv":{"id":45,"version":0,
"jour":"2014-07-08","idClient":1,"idCreneau":1}}

Cevapta ne müşterinin adı ne de randevu saati belirtilmiştir; sadece kimlik numaraları yer almaktadır.

Örnek 2:

URL
/getCreneauById/455
Réponse
{"status":2,"messages":["Rv [455] inexistant"],"rv":null}

3.5.12. Randevu ekle

URL [/ajouterRv], randevu eklemeye olanak tanır. Bu ekleme için gerekli bilgiler (gün, zaman aralığı ve müşteri) bir HTTP POST isteği aracılığıyla iletilir. [Advanced Rest Client] aracıyla bu isteğin nasıl gerçekleştirileceğini gösteriyoruz.

Image

  • [1]'te, sorgulanan URL;
  • [2]'te, bu sorgu bir POST tarafından sorgulanır;
  • [3-4]'te, sunucuya gönderilen değerlerin jSON biçiminde bir dize olarak gönderildiği belirtilir;
  • [4]'te, kimlik doğrulama başlığı HTTP;
  • [5]'te, POST tarafından iletilen bilgiler yer alır. Bu, aşağıdakileri içeren bir jSON dizesidir:
    • [jour]: randevu günü 'yyyy-aa-gg' biçiminde,
    • [idClient]: randevu alınan müşterinin kimlik numarası,
    • [idCreneau]: randevu zaman aralığının kimliği. Bir zaman aralığı belirli bir doktora ait olduğundan, bu şekilde doktor da belirtilmiş olur;
  • [6] ile istek gönderilir;

Gönderilen jSON dizesi, aşağıdaki [PostAjouterRv] türündeki nesneye aittir:


public class PostAjouterRv {

  // gönderi verileri
  private String jour;
  private long idClient;
  private long idCreneau;

  // oluşturucular
  public PostAjouterRv() {

  }

  public PostAjouterRv(String jour, long idCreneau, long idClient) {
    this.jour = jour;
    this.idClient = idClient;
    this.idCreneau = idCreneau;
  }

  // getter ve setter'lar
  ...
}

Sunucunun yanıtı, [Response<Rv>] [int status; List<String> messages; Rv rv] türündedir; burada [rv], eklenen randevudur.

Yukarıdaki isteğe sunucunun yanıtı şöyledir:

 

Yukarıda, bazı bilgilerin [idClient, idCreneau] alanında doldurulmamış olduğu, ancak [client] ve [creneau] alanlarında bulunduğu görülmektedir. Önemli bilgi, eklenen randevunun kimliğidir (209). Web hizmeti, yalnızca bu bilgiyi geri göndermekle yetinebilirdi.

3.5.13. Randevuyu silme

Bu işlem de bir POST ile gerçekleştirilir:

URL
/supprimerRv
POST
{'idRv':idRv}
Réponse

Response<RV> :[int status ; List<String> messages ; Rv rv]

Yayınlanan değer, aşağıdaki [PostSupprimerRv] türündeki nesnenin jSON dizesidir:


public class PostSupprimerRv {

  // gönderim verileri
  private long idRv;

  // yapıcılar
  public PostSupprimerRv() {

  }

  public PostSupprimerRv(long idRv) {
    this.idRv = idRv;
  }

  // getter ve setter'lar
  ...
}
  • 4. satırda, [idRv] silinecek randevunun kimliğidir.

Örnek 1:

URL
/supprimerRv
POST
{"idRv":209}
Réponse
{"status":0,"messages":null,"rv":null}

209 numaralı randevu, [status=0] nedeniyle iptal edilmiştir.

Örnek 2:

URL
/supprimerRv
POST
{"idRv":650}
Réponse
{"status":2,"messages":["Rv [650] inexistant"],"rv":null}

3.6. Android istemcisi

Image

[1] sunucusu ayrıntılı olarak ele alınmış ve çalışır hale getirilmiş olduğuna göre, şimdi [2] Android istemcisini inceleyeceğiz.

3.6.1. Android Studio projesinin mimarisi

Proje, [client-android-skel] projesinin mimarisini benimsemektedir (bkz. paragraf 1.17). Yukarıdaki Android istemci mimarisinde üç blok ayırt edilebilir:

  • web hizmeti ile iletişimi sağlayan [DAO] katmanı;
  • kullanıcıyla iletişimi sağlayan [vues] katmanları;
  • önceki iki blok arasında bağlantı kuran [activité]. Görünümler, [DAO] katmanından haberdar değildir. Yalnızca etkinlikle iletişim kurarlar.

Bu mimari, Android istemcisinin Android Studio projesindeki mimariye de yansımaktadır:

 
  • [activity] paketi, aktiviteyi uygular;
  • [architecture] paketi, daha önce geliştirdiğimiz mimari öğeleri içerir;
  • [dao] paketi, [DAO] katmanını uygular;
  • [fragments] paketi, [vues]'i uygular;

3.6.2. Projenin özelleştirilmesi

  

[architecture / custom] klasörü, mimarinin özelleştirilebilir öğelerini içerir.

[IMainActivity] arayüzü şu şekildedir:


package client.android.architecture.custom;

import client.android.architecture.core.ISession;
import client.android.dao.service.IDao;

public interface IMainActivity extends IDao {

  // oturum erişimi
  ISession getSession();

  // görünüm değiştirme
  void navigateToView(int position, ISession.Action action);

  // bekleme yönetimi
  void beginWaiting();

  void cancelWaiting();

  // uygulama sabitleri -------------------------------------

  // hata ayıklama modu
  boolean IS_DEBUG_ENABLED = true;

  // sunucu yanıtı için maksimum bekleme süresi
  int TIMEOUT = 1000;

  // istemci isteğinin yürütülmesinden önceki bekleme süresi
  int DELAY = 000;

  // temel kimlik doğrulama
  boolean IS_BASIC_AUTHENTIFICATION_NEEDED = true;

  // parçaların bitişikliği
  int OFF_SCREEN_PAGE_LIMIT = 1;

  // sekme çubuğu
  boolean ARE_TABS_NEEDED = false;

  // bekleme görüntüsü
  boolean IS_WAITING_ICON_NEEDED = true;

  // uygulamanın parça sayısı
  int FRAGMENTS_COUNT = 4;

  // görüntüleme sayısı
  int VUE_CONFIG = 0;
  int VUE_ACCUEIL = 1;
  int VUE_AGENDA = 2;
  int VUE_AJOUT_RV = 3;
}
  • 25. ve 28. satırlar: [DAO] katmanının özelleştirilmesi;
  • 31. satır: bu uygulama sunucuya kimlik doğrulamalı erişim sağlar;
  • satır 40: bir bekleme görüntüsü gereklidir;
  • 43. satır: Uygulamanın dört parçası vardır;
  • satır 46-49: dört parçanın numaraları;
  • 37. satır: sekme yoktur;

Parça raporlarının temel sınıfı [CoreState] şu şekilde olacaktır:


package client.android.architecture.custom;

import client.android.architecture.core.MenuItemState;
import client.android.fragments.state.AccueilFragmentState;
import client.android.fragments.state.AgendaFragmentState;
import client.android.fragments.state.AjoutRvFragmentState;
import client.android.fragments.state.ConfigFragmentState;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({
  @JsonSubTypes.Type(value = AccueilFragmentState.class),
  @JsonSubTypes.Type(value = AgendaFragmentState.class),
  @JsonSubTypes.Type(value = AjoutRvFragmentState.class),
  @JsonSubTypes.Type(value = ConfigFragmentState.class)
}
)
public class CoreState {
  // fragman ziyaret edildi mi, edilmedi mi
  protected boolean hasBeenVisited = false;
  // parçanın menüsünün durumu (varsa)
  protected MenuItemState[] menuOptionsState;

  // getter ve setter'lar
...
}
  • 15-18. satırlar: Dört parçanın durumu şöyledir:
  

Son olarak, oturum parçalar arasında paylaşılan verileri içerir:


package client.android.architecture.custom;

import client.android.architecture.core.AbstractSession;
import client.android.dao.entities.AgendaMedecinJour;
import client.android.dao.entities.Client;
import client.android.dao.entities.Medecin;
import client.android.fragments.state.AccueilFragmentState;
import client.android.fragments.state.AgendaFragmentState;
import client.android.fragments.state.AjoutRvFragmentState;
import client.android.fragments.state.ConfigFragmentState;

import java.util.List;

public class Session extends AbstractSession {
  // jSON'te serileştirilemeyen öğeler @JsonIgnore ek etiketine sahip olmalıdır

  // doktor listesi
  private List<Medecin> médecins;
  // müşteri listesi
  private List<Client> clients;
  // belirli bir gün için bir doktorun randevu takvimi
  private AgendaMedecinJour agenda;
  // takvimde tıklanan öğenin konumu
  private int position;
  // Randevu günü İngilizce "yyyy-MM-dd" biçiminde
  private String dayRv;
  // randevu günü Fransızca "dd-MM-yyyy" biçiminde
  private String jourRv;

  // getter ve setter'lar
...
}
  • satır 17-28: oturum altı bilgiyi saklar. Bunların rolünü gerektiğinde açıklayacağız.

3.6.3. [DAO] katmanı

  • [1]'te, sunucu yanıtlarında kapsüllenmiş varlıklar. Bunlar 3.5. paragrafta tanıtılmıştır;
  • [2]'te, sunucu ile iletişimi yöneten istemci bileşenleri;

[1] öğelerine tekrar değinmeyeceğiz. Bunlar daha önce ele alınmıştı. Okuyucunun gerekirse 3.5. paragrafa geri dönmesi önerilir. [service] paketinin uygulamasını inceleyeceğiz. Bu, bizi istemci ile sunucu arasındaki güvenli iletişimlerin uygulamasını da ele almaya yönlendirecektir.

3.6.3.1. İstemci / sunucu iletişiminin uygulaması

  

[WebClient] sınıfı, bir AA bileşenidir ve aşağıdakileri tanımlar:

  • web hizmeti tarafından sunulan URL bileşenlerini;
  • bunların parametrelerini;
  • yanıtlarını;

package rdvmedecins.android.dao.service;

import rdvmedecins.android.dao.entities.*;
import org.androidannotations.rest.spring.annotations.*;
import org.androidannotations.rest.spring.api.RestClientRootUrl;
import org.androidannotations.rest.spring.api.RestClientSupport;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@Rest(converters = {MappingJackson2HttpMessageConverter.class})
public interface WebClient extends RestClientRootUrl, RestClientSupport {

  // RestTemplate
  public void setRestTemplate(RestTemplate restTemplate);

  // doktor listesi
  @Get("/getAllMedecins")
  public Response<List<Medecin>> getAllMedecins();

  // müşteri listesi
  @Get("/getAllClients")
  public Response<List<Client>> getAllClients();

  // bir doktorun randevu aralıkları listesi
  @Get("/getAllCreneaux/{idMedecin}")
  public Response<List<Creneau>> getAllCreneaux(@Path long idMedecin);

  // bir doktorun randevu listesi
  @Get("/getRvMedecinJour/{idMedecin}/{jour}")
  public Response<List<Rv>> getRvMedecinJour(@Path long idMedecin, @Path String jour);

  // Müşteri
  @Get("/getClientById/{id}")
  public Response<Client> getClientById(@Path long id);

  // Doktor
  @Get("/getMedecinById/{id}")
  public Response<Medecin> getMedecinById(@Path long id);

  // Randevu
  @Get("/getRvById/{id}")
  public Response<Rv> getRvById(@Path long id);

  // Randevu aralığı
  @Get("/getCreneauById/{id}")
  public Response<Creneau> getCreneauById(@Path long id);

  // ekle RV
  @Post("/ajouterRv")
  public Response<Rv> ajouterRv(@Body PostAjouterRv post);

  // Randevu sil
  @Post("/supprimerRv")
  public Response<Rv> supprimerRv(@Body PostSupprimerRv post);

  // Bir doktorun randevu takvimini al
  @Get(value = "/getAgendaMedecinJour/{idMedecin}/{jour}")
  public Response<AgendaMedecinJour> getAgendaMedecinJour(@Path long idMedecin, @Path String jour);

}
  • 19-60. satırlar: 3.5. paragrafta incelenen tüm URL bileşenleri burada yer almaktadır;
  • 16. satır: istemci/sunucu iletişiminin dayandığı [Spring Android] bileşeninin [RestTemplate] bileşeni;

3.6.3.2. [IDao] arayüzü

  

[DAO] katmanının [IDao] arayüzü şöyledir:


package rdvmedecins.android.dao.service;

import rdvmedecins.android.dao.entities.*;
import rx.Observable;

import java.util.List;

public interface IDao {
  // Web hizmetinin URL'si
  public void setUrlServiceWebJson(String url);

  // kullanıcı
  public void setUser(String user, String mdp);

  // istemci zaman aşımı
  public void setTimeout(int timeout);

  // müşteri listesi
  public Observable<List<Client>> getAllClients();

  // Doktor listesi
  public Observable<List<Medecin>> getAllMedecins();

  // Bir doktorun randevu aralıkları listesi
  public Observable<List<Creneau>> getAllCreneaux(long idMedecin);

  // belirli bir günde bir doktorun randevu listesi
  public Observable<List<Rv>> getRvMedecinJour(long idMedecin, String jour);

  // kimlik numarasıyla bir müşteriyi bul
  public Observable<Client> getClientById(long id);

  // kimlik numarasıyla bir doktoru bulma
  public Observable<Medecin> getMedecinById(long id);

  // ID'siyle bir randevuyu bulma
  public Observable<Rv> getRvById(long id);

  // ID'siyle tanımlanan bir zaman aralığını bulma
  public Observable<Creneau> getCreneauById(long id);

  // bir RV eklemek
  public Observable<Rv> ajouterRv(String jour, long idCreneau, long idClient);

  // RV'i sil
  public Observable<Rv> supprimerRv(long idRv);

  // meslek
  public Observable<AgendaMedecinJour> getAgendaMedecinJour(long idMedecin, String jour);

  // hata ayıklama modu
  void setDebugMode(boolean isDebugEnabled);
}
  • 10. satır: web hizmeti / jSON için URL değerini belirlemek için;
  • 13. satır: istemci/sunucu iletişiminin kullanıcısını belirlemek için. [user] kullanıcı kimliği, [mdp] ise şifresidir;
  • 16. satır: sunucunun yanıtı için maksimum bekleme süresini belirlemek amacıyla;
  • satır 18-49: Web hizmeti tarafından sunulan her bir URL'e bir yöntem karşılık gelir. Bunlar, AA [WebClient] bileşenindeki aynı adlı yöntemlerin imzasını kullanır;
  • 52. satır: [DAO] katmanının debug modunu kontrol etmek için;

3.6.3.3. [Dao] sınıfı

  

Önceki [IDao] arayüzünün [DAO] uygulaması şu şekildedir:


package client.android.dao.service;

import android.util.Log;
import client.android.dao.entities.*;
import org.androidannotations.annotations.AfterInject;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EBean;
import org.androidannotations.rest.spring.annotations.RestService;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import rx.Observable;

import java.util.ArrayList;
import java.util.List;

@EBean(scope = EBean.Scope.Singleton)
public class Dao extends AbstractDao implements IDao {

  // web hizmeti istemcisi
  @RestService
  protected WebClient webClient;
  // güvenlik
  @Bean
  protected MyAuthInterceptor authInterceptor;
  // RestTemplate
  private RestTemplate restTemplate;
  // RestTemplate fabrikası
  private SimpleClientHttpRequestFactory factory;

  @AfterInject
  public void afterInject() {
    ...
  }

  @Override
  public void setUrlServiceWebJson(String url) {
    ...
  }

  @Override
  public void setUser(String user, String mdp) {
    ...
  }

  @Override
  public void setTimeout(int timeout) {
    ...
  }

  @Override
  public void setBasicAuthentification(boolean isBasicAuthentificationNeeded) {
    if (isDebugEnabled) {
      Log.d(className, String.format("setBasicAuthentification thread=%s, isBasicAuthentificationNeeded=%s", Thread.currentThread().getName(), isBasicAuthentificationNeeded));
    }
    // kimlik doğrulama engelleyicisi mi?
    if (isBasicAuthentificationNeeded) {
      // kimlik doğrulama engelleyicisi ekleniyor
      List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
      interceptors.add(authInterceptor);
      restTemplate.setInterceptors(interceptors);
    }

  }

  // özel yöntemler -------------------------------------------------
  private void log(String message) {
    if (isDebugEnabled) {
      Log.d(className, message);
    }
  }

  // IDao arayüzünün uygulanması --------------------------------------------------------------------
  @Override
  public Observable<Response<List<Client>>> getAllClients() {
    // günlük
    log("getAllClients");
    // sonuç
    return getResponse(new IRequest<Response<List<Client>>>() {
      @Override
      public Response<List<Client>> getResponse() {
        return webClient.getAllClients();
      }
    });
  }

  @Override
  public Observable<Response<List<Medecin>>> getAllMedecins() {
    // günlük
    log("getAllMedecins");
    // sonuç
    return getResponse(new IRequest<Response<List<Medecin>>>() {
      @Override
      public Response<List<Medecin>> getResponse() {
        return webClient.getAllMedecins();
      }
    });
  }

  @Override
  public Observable<Response<List<Creneau>>> getAllCreneaux(final long idMedecin) {
    // günlük
    log("getAllCreneaux");
    // sonuç
    return getResponse(new IRequest<Response<List<Creneau>>>() {
      @Override
      public Response<List<Creneau>> getResponse() {
        return webClient.getAllCreneaux(idMedecin);
      }
    });
  }

  @Override
  public Observable<Response<List<Rv>>> getRvMedecinJour(final long idMedecin, final String jour) {
    // günlük
    log("getRvMedecinJour");
    // sonuç
    return getResponse(new IRequest<Response<List<Rv>>>() {
      @Override
      public Response<List<Rv>> getResponse() {
        return webClient.getRvMedecinJour(idMedecin, jour);
      }
    });
  }

  @Override
  public Observable<Response<Client>> getClientById(final long id) {
    // günlük
    log("getClientById");
    // sonuç
    return getResponse(new IRequest<Response<Client>>() {
      @Override
      public Response<Client> getResponse() {
        return webClient.getClientById(id);
      }
    });
  }

  @Override
  public Observable<Response<Medecin>> getMedecinById(final long id) {
    // günlük
    log("getMedecinById");
    // sonuç
    return getResponse(new IRequest<Response<Medecin>>() {
      @Override
      public Response<Medecin> getResponse() {
        return webClient.getMedecinById(id);
      }
    });
  }

  @Override
  public Observable<Response<Rv>> getRvById(final long id) {
    // günlük
    log("getRvById");
    // sonuç
    return getResponse(new IRequest<Response<Rv>>() {
      @Override
      public Response<Rv> getResponse() {
        return webClient.getRvById(id);
      }
    });
  }

  @Override
  public Observable<Response<Creneau>> getCreneauById(final long id) {
    // günlük
    log("getCreneauById");
    // sonuç
    return getResponse(new IRequest<Response<Creneau>>() {
      @Override
      public Response<Creneau> getResponse() {
        return webClient.getCreneauById(id);
      }
    });
  }

  @Override
  public Observable<Response<Rv>> ajouterRv(final String jour, final long idCreneau, final long idClient) {
    // günlük
    log("ajouterRv");
    // sonuç
    return getResponse(new IRequest<Response<Rv>>() {
      @Override
      public Response<Rv> getResponse() {
        return webClient.ajouterRv(new PostAjouterRv(jour, idCreneau, idClient));
      }
    });
  }

  @Override
  public Observable<Response<Rv>> supprimerRv(final long idRv) {
    // günlük
    log("supprimerRv");
    // sonuç
    return getResponse(new IRequest<Response<Rv>>() {
      @Override
      public Response<Rv> getResponse() {
        return webClient.supprimerRv(new PostSupprimerRv(idRv));
      }
    });
  }

  @Override
  public Observable<Response<AgendaMedecinJour>> getAgendaMedecinJour(final long idMedecin, final String jour) {
    // günlük
    log("getAgendaMedecinJour");
    // sonuç
    return getResponse(new IRequest<Response<AgendaMedecinJour>>() {
      @Override
      public Response<AgendaMedecinJour> getResponse() {
        return webClient.getAgendaMedecinJour(idMedecin, jour);
      }
    });
  }

}
  • 18-72. satırlar: [client-android-skel] projesindeki [Dao] sınıfında temel olarak bulunan satırlardır;
  • 74-216. satırlar: [IDao] arayüzünün uygulamasıdır. Web hizmeti tarafından sunulan ve URL'i sorgulayan yöntemler, bu sorgulamayı AA ve [WebClient] bileşenlerine devreder (satır 22-23);
  • satır 58-63: İstemci/sunucu iletişimi temel düzeyde bir yetkilendirme ile doğrulanıyorsa, [RestTemplate] bileşenine bir ara alıcı eklenir. Bu işlem, [RestTemplate] bileşeni tarafından gönderilen tüm HTTP isteklerinin [MyAuthInterceptor] sınıfı tarafından yakalanmasını sağlayacaktır (satır 25-26);

[MyAuthInterceptor] sınıfı şu şekildedir:


package rdvmedecins.android.dao.security;

import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EBean;
import org.springframework.http.HttpAuthentication;
import org.springframework.http.HttpBasicAuthentication;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

import java.io.IOException;

@EBean(scope = EBean.Scope.Singleton)
public class MyAuthInterceptor implements ClientHttpRequestInterceptor {

  // kullanıcı
  private String user;
  private String mdp;

  public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
    HttpHeaders headers = request.getHeaders();
    HttpAuthentication auth = new HttpBasicAuthentication(user, mdp);
    headers.setAuthorization(auth);
    return execution.execute(request, body);
  }

  public void setUser(String user, String mdp) {
    this.user = user;
    this.mdp = mdp;
  }
}
  • 15. satır: [MyAuthInterceptor] sınıfı, [singleton] türünde bir AA bileşenidir;
  • 16. satır: [MyAuthInterceptor] sınıfı, Spring [ClientHttpRequestInterceptor] arayüzünü genişletir. Bu arayüzün 22. satırda yer alan [intercept] adlı bir yöntemi vardır. Bu arayüz, istemciden gelen tüm HTTP isteklerini yakalamak için genişletilir. [intercept] yöntemi üç parametre alır;
    • [HtpRequest request]: yakalanan HTTP isteği,
    • [byte[] body]: varsa istek gövdesi (örneğin, POST ile gönderilen değerler),
    • [ClientHttpRequestExecution execution]: isteği yürüten Spring bileşeni;

Android istemcisinden gelen tüm HTTP isteklerini yakalayıp, bunlara 3.5. paragrafta açıklanan HTTP kimlik doğrulama başlığını ekliyoruz.

  • 23. satır: Yakalanan isteğin HTTP başlıklarını alıyoruz;
  • 24. satır: HTTP kimlik doğrulama başlığını oluşturuyoruz. Kullanılan kimlik doğrulama yöntemi ('user:mdp' dizesinin base64 kodlaması), Spring [HttpBasicAuthentication] sınıfı tarafından sağlanır;
  • 25. satır: Az önce oluşturduğumuz kimlik doğrulama başlığı, yakalanan isteğin mevcut başlıklarına eklenir;
  • 26. satır: Yakalanan isteğin yürütülmesine devam edilir. Özetlemek gerekirse, yakalanan istek kimlik doğrulama başlığıyla zenginleştirilmiştir;

[IDao] arayüzündeki yöntemlerin uygulamaları, hepsi aynı şablona göre yapılmıştır. [getAgendaMedecinJour] yöntemini örnek olarak alalım:


  @Override
  public Observable<Response<AgendaMedecinJour>> getAgendaMedecinJour(final long idMedecin, final String jour) {
    // günlük
    log("getAgendaMedecinJour");
    // sonuç
    return getResponse(new IRequest<Response<AgendaMedecinJour>>() {
      @Override
      public Response<AgendaMedecinJour> getResponse() {
        return webClient.getAgendaMedecinJour(idMedecin, jour);
      }
    });
}
  • 2. satır: Yöntem iki parametre bekler:
    • [idMedecin]: randevu takvimini almak istediğimiz doktorun kimlik numarası;
    • [jour]: takvimini görmek istediğimiz gün;
  • 6. satır: üst sınıf olan [AbstractDao]'in [getResponse] yöntemini çağırıyoruz. Bu yöntem, [IRequest<T>] türünde bir parametre bekler; burada T, 2. satırdaki [getAgendaMedecinJour] yöntemi tarafından döndürülen türdür (bu örnekte [Response<AgendaMedecinJour>]). [IRequest] arayüzünde tek bir yöntem vardır: [getResponse] (8. satır);
  • satır 8-10: [IRequest.getResponse] yönteminin uygulaması. Bu yöntem, 2. satırdaki [getAgendaMedecinJour] yönteminin beklediği, [Response<AgendaMedecinJour>] türündeki sonucu döndürmelidir;
  • 9. satır: yanıt, [webClient.getAgendaMedecinJour] yöntemi tarafından döndürülür:

  // bir doktorun randevu takvimini almak
  @Get(value = "/getAgendaMedecinJour/{idMedecin}/{jour}")
Response<AgendaMedecinJour> getAgendaMedecinJour(@Path long idMedecin, @Path String jour);
  1. satırda kullanılan parametreler, 2. satırdaki [getAgendaMedecinJour] yöntemine aktarılan parametrelerdir. Bu nedenle, bu parametrelerin final özniteliğine sahip olması gerekir;

3.6.4. [MainActivity] etkinliği

Serveur
  

[MainActivity] sınıfı şöyledir:


package client.android.activity;

import android.util.Log;
import client.android.architecture.core.AbstractActivity;
import client.android.architecture.core.AbstractFragment;
import client.android.architecture.custom.IMainActivity;
import client.android.dao.entities.*;
import client.android.dao.service.Dao;
import client.android.dao.service.IDao;
import client.android.dao.service.Response;
import client.android.fragments.behavior.AccueilFragment_;
import client.android.fragments.behavior.AgendaFragment_;
import client.android.fragments.behavior.AjoutRvFragment_;
import client.android.fragments.behavior.ConfigFragment_;
import org.androidannotations.annotations.Bean;
import org.androidannotations.annotations.EActivity;
import rx.Observable;

import java.util.List;

@EActivity
public class MainActivity extends AbstractActivity {

  // katman [DAO]
  @Bean(Dao.class)
  protected IDao dao;

  // ebeveyn sınıfı ---------------------------------------
  @Override
  protected void onCreateActivity() {
    // günlük
    if (IS_DEBUG_ENABLED) {
      Log.d(className, "onCreateActivity");
    }
  }

  @Override
  protected IDao getDao() {
    return dao;
  }

  @Override
  protected AbstractFragment[] getFragments() {
    AbstractFragment[] fragments= new AbstractFragment[]{new ConfigFragment_(), new AccueilFragment_(), new AgendaFragment_(), new AjoutRvFragment_()};
    return fragments;
  }

  @Override
  protected CharSequence getFragmentTitle(int position) {
    return null;
  }

  @Override
  protected void navigateOnTabSelected(int position) {

  }

  @Override
  protected int getFirstView() {
    return IMainActivity.VUE_CONFIG;
  }

  // arayüz IDao -----------------------------------------------------
...

  @Override
  public Observable<Response<List<Client>>> getAllClients() {
    return dao.getAllClients();
  }

  @Override
  public Observable<Response<List<Medecin>>> getAllMedecins() {
    return dao.getAllMedecins();
  }

  @Override
  public Observable<Response<List<Creneau>>> getAllCreneaux(long idMedecin) {
    return dao.getAllCreneaux(idMedecin);
  }

  @Override
  public Observable<Response<List<Rv>>> getRvMedecinJour(long idMedecin, String jour) {
    return dao.getRvMedecinJour(idMedecin, jour);
  }

  @Override
  public Observable<Response<Client>> getClientById(long id) {
    return dao.getClientById(id);
  }

  @Override
  public Observable<Response<Medecin>> getMedecinById(long id) {
    return dao.getMedecinById(id);
  }

  @Override
  public Observable<Response<Rv>> getRvById(long id) {
    return dao.getRvById(id);
  }

  @Override
  public Observable<Response<Creneau>> getCreneauById(long id) {
    return dao.getCreneauById(id);
  }

  @Override
  public Observable<Response<Rv>> ajouterRv(String jour, long idCreneau, long idClient) {
    return dao.ajouterRv(jour, idCreneau, idClient);
  }

  @Override
  public Observable<Response<Rv>> supprimerRv(long idRv) {
    return dao.supprimerRv(idRv);
  }

  @Override
  public Observable<Response<AgendaMedecinJour>> getAgendaMedecinJour(long idMedecin, String jour) {
    return dao.getAgendaMedecinJour(idMedecin, jour);
  }
}
  • 21-66. satırlar: Bu satırlar, [client-android-skel] şablonunda varsayılan olarak sağlanır;
  • 66-119. satırlar: [IDao] arayüzünün uygulaması. Tüm yöntemler, işi 26. satırdaki [DAO] katmanına devreder;
  • satır 42-46: [getFragments] yöntemi, uygulamanın dört parçasını içeren diziyi döndürür;
  • 58-61. satırlar: Yapılandırma görünümü, uygulama başlatıldığında görüntülenen ilk görünümdür;

3.6.5. Oturum

  

[Session] sınıfı, parçalar arasında aktarılması gereken bilgileri depolamak için kullanılır. Şu şekildedir:


package rdvmedecins.android.architecture;

import rdvmedecins.android.dao.entities.AgendaMedecinJour;
import rdvmedecins.android.dao.entities.Client;
import rdvmedecins.android.dao.entities.Medecin;
import org.androidannotations.annotations.EBean;

import java.util.List;

@EBean(scope = EBean.Scope.Singleton)
public class Session {
  // doktor listesi
  private List<Medecin> médecins;
  // müşteri listesi
  private List<Client> clients;
  // takvim
  private AgendaMedecinJour agenda;
  // takvimde tıklanan öğenin konumu
  private int position;
  // Randevu tarihi İngilizce "yyyy-MM-dd" biçiminde
  private String dayRv;
  // randevu günü Fransızca biçiminde "dd-MM-yyyy"
  private String jourRv;


  // getter ve setter'lar
...
}
  • 10. satır: [Session] sınıfı, tek bir örneği oluşturulmuş bir AA bileşenidir;
  • satır 12-15: Bu vaka çalışmasında, doktor ve müşteri listelerinin değişmeyeceği varsayılacaktır. Bu listeler, uygulamanın başlatılması sırasında istenecek ve parçaların bunları kullanabilmesi için oturumda depolanacaktır;
  • satır 20-23: Randevu için istenen gün. Bu, Android istemcisinde Fransızca biçiminde (satır 23), sunucu ile iletişimde ise İngilizce biçiminde (satır 21) olmak üzere iki şekilde işlenir;
  • satır 19: takvimde tıklanan öğenin (ekle/sil bağlantısı) konumu;

3.6.6. Yapılandırma Görünümünün Yönetimi

3.6.6.1. Görünüm

Yapılandırma görünümü, uygulamanın başlatılmasıyla birlikte görüntülenen görünümdür:

Image

Görsel arayüzün öğeleri şunlardır:

Type
Nom
1
EditText
edtUrlServiceRest
3
EditText
edtUtilisateur
5
EditText
edtMdp
2
TextView
txtErrorUrlServiceRest
3
TextView
txtErrorUtilisateur

3.6.6.2. Parça

Yapılandırma görünümü, aşağıdaki [ConfigFragment] parçası tarafından yönetilir:

 

package client.android.fragments.behavior;

import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import client.android.R;
import client.android.architecture.core.AbstractFragment;
import client.android.architecture.core.ISession;
import client.android.architecture.core.MenuItemState;
import client.android.architecture.custom.CoreState;
import client.android.architecture.custom.IMainActivity;
import client.android.dao.entities.Client;
import client.android.dao.entities.Medecin;
import client.android.dao.service.Response;
import client.android.fragments.state.ConfigFragmentState;
import org.androidannotations.annotations.*;
import rx.functions.Action1;

import java.net.URI;
import java.util.List;

@EFragment(R.layout.config)
@OptionsMenu(R.menu.menu_config)
public class ConfigFragment extends AbstractFragment {

  // görsel arayüz öğeleri
  @ViewById(R.id.edt_urlServiceRest)
  protected EditText edtUrlServiceRest;
  @ViewById(R.id.txt_errorUrlServiceRest)
  protected TextView txtErrorUrlServiceRest;
  @ViewById(R.id.txt_errorUtilisateur)
  protected TextView txtErrorUtilisateur;
  @ViewById(R.id.edt_utilisateur)
  protected EditText edtUtilisateur;
  @ViewById(R.id.edt_mdp)
  protected EditText edtMdp;

  // girişler
  private String urlServiceRest;
  private String utilisateur;
  private String mdp;

  // sayfanın doğrulanması
  @OptionsItem(R.id.actionValider)
  protected void doValider() {
   ...
  }
..
  // üst sınıf yöntemlerinin uygulanması -------------------------------------------
 ...

}
  • 25. satır: Parça, aşağıdaki [menu_config] menüsüyle ilişkilendirilmiştir:
  

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      tools:context=".activity.MainActivity1">
  <item
    android:id="@+id/menuActions"
    app:showAsAction="ifRoom"
    android:title="@string/menuActions">
    <menu>
      <item
        android:id="@+id/actionValider"
        android:title="@string/actionValider"/>
      <item
        android:id="@+id/actionAnnuler"
        android:title="@string/actionAnnuler"/>
    </menu>
  </item>

</menu>
  • 28-38. satırlar: görsel arayüz öğeleri;
  • 41-43. satırlar: formdaki üç giriş alanı;

[Valider] menü seçeneğine tıklama, [doValider] yöntemi tarafından yönetilir:


// sayfanın doğrulanması
  @OptionsItem(R.id.actionValider)
  protected void doValider() {
    // önceki hata mesajları gizlenir
    txtErrorUrlServiceRest.setVisibility(View.INVISIBLE);
    txtErrorUtilisateur.setVisibility(View.INVISIBLE);
    // girişlerin geçerliliği test ediliyor
    if (!isPageValid()) {
      return;
    }
    // web hizmetinin URL bilgisi girilir
    mainActivity.setUrlServiceWebJson(urlServiceRest);
    // kullanıcı bilgisi girilir
    mainActivity.setUser(utilisateur, mdp);
    // bekleme başlıyor - 2 asenkron görev başlatılacak
    beginWaiting(2);
    // doktorlar
    executeInBackground(mainActivity.getAllMedecins(), new Action1<Response<List<Medecin>>>() {
      @Override
      public void call(Response<List<Medecin>> responseMedecins) {
        // yanıt alınır
        consumeMedecins(responseMedecins);
      }
    });
    // müşteriler
    executeInBackground(mainActivity.getAllClients(), new Action1<Response<List<Client>>>() {
      @Override
      public void call(Response<List<Client>> responseClients) {
        // yanıt işleniyor
        consumeClients(responseClients);
      }
    });
  }


  private void consumeMedecins(Response<List<Medecin>> responseMedecins) {
    // günlük
    if (isDebugEnabled) {
      Log.d(className, "consume médecins");
    }
    // hata mı?
    if (responseMedecins.getStatus() != 0) {
      // mesaj
      showAlert(responseMedecins.getMessages());
      // iptal
      doAnnuler();
      // UI'e geri dön
      return;
    }
    // oturumda doktorlar kaydedilir
    session.setMédecins(responseMedecins.getBody());
  }

  private void consumeClients(Response<List<Client>> responseClients) {
    // günlük
    if (isDebugEnabled) {
      Log.d(className, "consume clients");
    }
    // hata mı?
    if (responseClients.getStatus() != 0) {
      // mesaj
      showAlert(responseClients.getMessages());
      // iptal
      doAnnuler();
      // UI'e geri dön
      return;
    }
    // müşteriler oturumda kaydedilir
    session.setClients(responseClients.getBody());
  }
  • satır 8-10: Formdaki üç girişin geçerliliği kontrol edilir. Form geçersizse, işlem devam etmez;
  • satır 11-14: [DAO] katmanı için gerekli girdiler faaliyete aktarılır;
  • 16. satır: üst sınıfa iki asenkron görevin başlatılacağı bildirilir ve bekleme hazırlığı yapılır;
  • satır 17-24: doktor listesi istenir;
  • 18. satır: [executeInBackground] yöntemi iki parametre bekler:
    • 18. satır: Yürütülecek ve gözlemlenecek süreç, [mainActivity.getAllMedecins()] yöntemi tarafından sağlanır;
    • satır 18-24: ikinci parametre, T'nin gözlemlenen işlem tarafından döndürülen tür olduğu (burada [Response<List<Medecin>>]) bir [Action1<T>] türü örneğidir
  • 22. satır: Yanıt alındığında, bu yanıt 36. satırdaki [consumeMedecins] yöntemine aktarılır;
  • satır 25-33: İlk asenkron görevi başlattıktan sonra, müşteri listesini istemek için ikinci bir görev başlatılır. Böylece paralel olarak çalışan iki görevimiz olacaktır;
  • 36-52. satırlar: Doktorlarla ilgili görevden yanıt alındı. Bu yanıt işlenir;
  • 42-49. satırlar: Öncelikle sunucunun yanıtın [status] alanında bir hata bildirip bildirmediğine bakılır;
  • 44. satır: Hata varsa, sunucunun yanıtın [messages] alanında yer alan mesajları görüntülüyoruz;
  • satır 46: tüm görevleri iptal ediyoruz;
  • satır 48: kullanıcı arayüzüne geri dönülür;
  • satır 51: hata yoksa, doktor listesi oturuma kaydedilir;

Girişlerin geçerliliği (satır 8) aşağıdaki yöntemle kontrol edilir:


  private boolean isPageValid() {
    // girilen verilerin geçerliliği kontrol ediliyor
    boolean erreur;
    URI service;
    // REST hizmetinin URL geçerliliği
    urlServiceRest = String.format("http://%s", edtUrlServiceRest.getText().toString().trim());
    try {
      service = new URI(urlServiceRest);
      erreur = service.getHost() == null || service.getPort() == -1;
    } catch (Exception ex) {
      // hata kaydedilir
      erreur = true;
    }
    if (erreur) {
      // hata mesajı görüntüleniyor
      txtErrorUrlServiceRest.setVisibility(View.VISIBLE);
    }
    // kullanıcı
    utilisateur = edtUtilisateur.getText().toString().trim();
    if (utilisateur.length() == 0) {
      // hata görüntüleniyor
      txtErrorUtilisateur.setVisibility(View.VISIBLE);
      // hata kaydediliyor
      erreur = true;
    }
    // şifre
    mdp = edtMdp.getText().toString().trim();
    // geri
    return !erreur;
}

[beginWaiting] yöntemi (satır 16) şu şekildedir:


  // bekleme başlıyor
  protected void beginWaiting(int numberOfRunningTasks) {
    // görevlerin başlatılması hazırlanıyor
    beginRunningTasks(numberOfRunningTasks);
    // düğmelerin ve menülerin durumu
    setAllMenuOptionsStates(false);
    setMenuOptionsStates(new MenuItemState[]{new MenuItemState(R.id.menuActions, true),new MenuItemState(R.id.actionAnnuler, true)});

}
  • 4. satır: üst görevde [numberOfRunningTasks] görevlerini başlatacağımız belirtilir;
  • 6. satır: menüdeki tüm seçenekler gizlenir;
  • 7. satır: Ardından [Actions/Annuler] seçeneğini görünür hale getirmek için;

[Annuler] menü seçeneğine tıklama, [doAnnuler] yöntemi tarafından yönetilir:


  @OptionsItem(R.id.actionAnnuler)
  protected void doAnnuler() {
    if (isDebugEnabled) {
      Log.d(className, "Annulation demandée");
    }
    // asenkron görevler iptal ediliyor
    cancelRunningTasks();
}
  • 8. satır: üst sınıftan asenkron görevleri iptal etmesi istenir;

3.6.6.3. Parçanın yaşam döngüsü yönetimi

Parçanın durumu şu şekildedir: [ConfigFragmentState]:


package client.android.fragments.state;

import client.android.architecture.custom.CoreState;

public class ConfigFragmentState extends CoreState {

  // iki hata mesajının görünürlüğü
  private boolean txtErrorUrlServiceRestVisible;
  private boolean txtErrorUtilisateurVisible;

  // getter ve setter'lar
...
}
  • ana sınıf talepte bulunduğunda, fragman iki hata mesajının görünürlüğünü kaydedecektir;

Parçanın yaşam döngüsü şu şekilde uygulanır:


// üst sınıf yöntemlerinin uygulanması -------------------------------------------
  @Override
  public CoreState saveFragment() {
    // parça durumunun kaydedilmesi
    ConfigFragmentState state = new ConfigFragmentState();
    state.setTxtErrorUrlServiceRestVisible(txtErrorUrlServiceRest.getVisibility() == View.VISIBLE);
    state.setTxtErrorUtilisateurVisible(txtErrorUtilisateur.getVisibility() == View.VISIBLE);
    return state;
  }

  @Override
  protected int getNumView() {
    return     IMainActivity.VUE_CONFIG;
  }

  @Override
  protected void initFragment(CoreState previousState) {

  }

  @Override
  protected void initView(CoreState previousState) {
    if (previousState == null) {
      // İlk ziyaret
      // hata mesajları gizlenir
      txtErrorUtilisateur.setVisibility(View.INVISIBLE);
      txtErrorUrlServiceRest.setVisibility(View.INVISIBLE);
      // menü
      initMenu();
    }
  }

  @Override
  protected void updateOnSubmit(CoreState previousState) {
  }

  @Override
  protected void updateOnRestore(CoreState previousState) {
    // hata mesajlarının görünürlüğünü geri yükleme
    ConfigFragmentState state = (ConfigFragmentState) previousState;
    // ilk ziyaret değil - hata mesajları yeniden gösterilir
    txtErrorUtilisateur.setVisibility(state.isTxtErrorUtilisateurVisible() ? View.VISIBLE : View.INVISIBLE);
    txtErrorUrlServiceRest.setVisibility(state.isTxtErrorUrlServiceRestVisible() ? View.VISIBLE : View.INVISIBLE);
  }


  @Override
  protected void notifyEndOfUpdates() {
  }

  @Override
  protected void notifyEndOfTasks(boolean runningTasksHaveBeenCanceled) {
    // menü
    initMenu();
    // sonraki görünüm?
    if (!runningTasksHaveBeenCanceled) {
      mainActivity.navigateToView(IMainActivity.VUE_ACCUEIL, ISession.Action.SUBMIT);
    }
  }

  // özel yöntemler ------------------------------------------------
  private void initMenu(){
    // menü durumu
    setAllMenuOptionsStates(true);
    setMenuOptionsStates(new MenuItemState[]{new MenuItemState(R.id.actionAnnuler, false)});
}
  • 2-9. satırlar: üst sınıfından talep geldiğinde, fragman iki hata mesajının durumunu kaydeder;
  • satır 11-14: parçanın numarası [IMainActivity.VUE_CONFIG]'tir;
  • satır 16-19: Fragman ilk kez oluşturulduğunda (previousState==null) veya sonraki seferlerde yeniden oluşturulduğunda (previousState !=null) yürütülür. Burada yapılacak bir şey yoktur;
  • satır 21-31: parçaya bağlı görünüm ilk kez oluşturulduğunda (previousState==null) veya sonraki seferlerde yeniden oluşturulduğunda (previousState !=null) yürütülür;
    • 24-29. satırlar: İlk ziyaret sırasında hata mesajları gizlenir ve [Annuler] eylemi olmadan menü görüntülenir (62-66. satırlar);
  • satır 33-35: [SUBMIT] işlemiyle parçaya ulaşıldığında yürütülür. Burada bu durum asla gerçekleşmez;
  • satır 37-44: [NAVIGATION] veya [RESTORE] işlemiyle parçaya ulaşıldığında yürütülür. Hata mesajlarının durumu önceki durumdan geri yüklenir;
  • satır 47-49: önceki tüm güncellemeler yapıldığında yürütülür. Yapılacak başka bir şey kalmaz;
  • satır 51-59: tüm asenkron görevler tamamlandığında yürütülür;
    • satır 53-54: menü varsayılan durumuna geri getirilir;
    • 56-58. satırlar: Görevler normal şekilde tamamlandıysa bir sonraki görünüme geçilir, aksi takdirde aynı görünümde kalınır;

3.6.7. Ana Sayfa Görünümünün Yönetimi

3.6.7.1. Görünüm

Ana sayfa görünümü şöyledir:

Image

Görsel arayüzün öğeleri şunlardır:

Type
Nom
1
Spinner
spinnerMedecins
2
DatePicker
edtJourRv

3.6.7.2. Parça

Ana sayfa, aşağıdaki [AccueilFragment] parçası tarafından yönetilir:

 

package client.android.fragments.behavior;

import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.Spinner;
import client.android.R;
import client.android.architecture.core.AbstractFragment;
import client.android.architecture.core.ISession;
import client.android.architecture.core.MenuItemState;
import client.android.architecture.custom.CoreState;
import client.android.architecture.custom.IMainActivity;
import client.android.dao.entities.AgendaMedecinJour;
import client.android.dao.entities.Medecin;
import client.android.dao.service.Response;
import client.android.fragments.state.AccueilFragmentState;
import org.androidannotations.annotations.*;
import rx.functions.Action1;

import java.util.Calendar;
import java.util.List;
import java.util.Locale;

@EFragment(R.layout.accueil)
@OptionsMenu(R.menu.menu_accueil)
public class AccueilFragment extends AbstractFragment {

  // görsel arayüz öğeleri
  @ViewById(R.id.spinnerMedecins)
  protected Spinner spinnerMedecins;
  @ViewById(R.id.edt_JourRv)
  protected DatePicker edtJourRv;

  // yerel veriler
  private List<Medecin> medecins;
  private Calendar calendrier;
  private String[] spinnerMedecinsDataSource;

  // sayfa doğrulama
  @OptionsItem(R.id.actionValider)
  protected void doValider() {
    ...
  }
...

  // üst sınıf yöntemlerinin uygulanması -------------------------------------
...
}
  • 26. satır: Parça, aşağıdaki [menu_accueil] menüsüyle ilişkilendirilmiştir:
  

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      tools:context=".activity.MainActivity1">
  <item
    android:id="@+id/menuActions"
    app:showAsAction="ifRoom"
    android:title="@string/menuActions">
    <menu>
      <item
        android:id="@+id/actionValider"
        android:title="@string/actionValider"/>
      <item
        android:id="@+id/actionAnnuler"
        android:title="@string/actionAnnuler"/>
    </menu>
  </item>
  <item
    android:id="@+id/menuNavigation"
    app:showAsAction="ifRoom"
    android:title="@string/menuNavigation">
    <menu>
      <item
        android:id="@+id/navigationToConfig"
        android:title="@string/navigationToConfig"/>
    </menu>
  </item>
</menu>
  • 31-34. satırlar: görsel arayüz öğeleri;
  • 37. satır: doktor listesi;
  • 38. satır: bir takvim;
  • satır 39: doktor seçici için veri kaynağı;

[Valider] bağlantısına tıklama, aşağıdaki [doValider] yöntemi tarafından yönetilir:


// sayfa doğrulama
  @OptionsItem(R.id.actionValider)
  protected void doValider() {
    // seçilen doktorun kimliği kaydedilir
    Long idMedecin = medecins.get(spinnerMedecins.getSelectedItemPosition()).getId();
    // gün, oturumda saklanır
    String jourRv = String.format(new Locale("Fr-fr"), "%02d-%02d-%04d", edtJourRv.getDayOfMonth(), edtJourRv.getMonth() + 1, edtJourRv.getYear());
    session.setJourRv(jourRv);
    // tarih biçimi yyyy-MM-dd olarak değiştirilir
    String dayRv = String.format(new Locale("Fr-fr"), "%04d-%02d-%02d", edtJourRv.getYear(), edtJourRv.getMonth() + 1, edtJourRv.getDayOfMonth());
    session.setDayRv(dayRv);
    // bekleme başlangıcı - 1 asenkron görev başlatılacak
    beginWaiting(1);
    // doktorun randevu takvimi isteniyor
    executeInBackground(mainActivity.getAgendaMedecinJour(idMedecin, dayRv), new Action1<Response<AgendaMedecinJour>>() {

      @Override
      public void call(Response<AgendaMedecinJour> responseAgendaMedecinJour) {
        // yanıt işleniyor
        consumeAgenda(responseAgendaMedecinJour);
      }
    });
  }

  private void consumeAgenda(Response<AgendaMedecinJour> responseAgendaMedecinJour) {
    // hata mı?
    if (responseAgendaMedecinJour.getStatus() != 0) {
      // mesaj
      showAlert(responseAgendaMedecinJour.getMessages());
      // iptal
      doAnnuler();
      // UI'e geri dönülüyor
      return;
    }
    // gündemi oturuma ekliyoruz
    session.setAgenda(responseAgendaMedecinJour.getBody());
  }
  • satır 5: seçilen doktorun kimliği alınır;
  • 7-8. satırlar: Seçilen tarih, Fransızca biçiminde oturuma eklenir;
  • 10-11. satırlar: Seçilen tarih, İngilizce formatında oturuma aktarılır;
  • 13. satır: ana sınıfa asenkron bir görev başlatılacağını bildirir ve bekleme işlemini hazırlar;
  • satır 15-22: doktorun randevu takvimi istenir;
    • satır 15: [executeInBackground] yöntemi iki parametre bekler:
      • satır 15: Yürütülecek ve gözlemlenecek süreç, [mainActivity.getAgendaMedecinJour(idMedecin, dayRv)] yöntemi tarafından sağlanır;
      • satır 15-22: ikinci parametre, T'nin gözlemlenen işlem tarafından döndürülen tür olduğu (burada [Response<AgendaMedecinJour>]) bir [Action1<T>] türü örneğidir
    • 20. satır: Yanıt alındığında, bu yanıt 25. satırdaki [consumeAgenda] yöntemine aktarılır;
  • 25-37. satırlar: Doktorun randevu takvimi alındı. Bu takvim işlenir;
  • 27-34. satırlar: Öncelikle sunucunun yanıtın [status] alanında bir hata bildirip bildirmediğine bakılır;
  • 29. satır: Hata varsa, sunucunun yanıtın [messages] alanında yer alan mesajları görüntülüyoruz;
  • satır 31: tüm görevleri iptal ediyoruz;
  • satır 33: kullanıcı arayüzüne geri dönülür;
  • satır 36: hata yoksa, ajanda oturuma alınır;

[beginWaiting] yöntemi (satır 13) şu şekildedir:


  // bekleme başlıyor
  protected void beginWaiting(int numberOfRunningTasks) {
    // görevlerin başlatılması için hazırlık yapılıyor
    beginRunningTasks(numberOfRunningTasks);
    // düğmelerin ve menülerin durumu
    setAllMenuOptionsStates(false);
    setMenuOptionsStates(new MenuItemState[]{new MenuItemState(R.id.menuActions, true),new MenuItemState(R.id.actionAnnuler, true)});

}
  • 4. satır: üst göreve, [numberOfRunningTasks] görevlerini başlatacağımız bildirilir;
  • 6. satır: menüdeki tüm seçenekler gizlenir;
  • 7. satır: Ardından [Actions/Annuler] seçeneğini görünür hale getirir;

[Annuler] menü seçeneğine tıklama, [doAnnuler] yöntemi tarafından yönetilir:


  @OptionsItem(R.id.actionAnnuler)
  protected void doAnnuler() {
    if (isDebugEnabled) {
      Log.d(className, "Annulation demandée");
    }
    // asenkron görevler iptal ediliyor
    cancelRunningTasks();
}
  • satır 8: üst sınıftan asenkron görevleri iptal etmesi istenir;

[Retour à la configuration] menü seçeneğine tıklama şu şekilde yönetilir:


  @OptionsItem(R.id.navigationToConfig)
  protected void navigationToConfig() {
    // yapılandırma görünümüne geçiliyor
    mainActivity.navigateToView(IMainActivity.VUE_CONFIG, ISession.Action.NAVIGATION);
}
  • 4. satır: [NAVIGATION] eylemiyle yapılandırma görünümüne geçilir. Bu, yapılandırma görünümünü bırakıldığı haliyle geri getirmek istendiği anlamına gelir;

3.6.7.3. Parça Yaşam Döngüsü Yönetimi

Parçanın durumu [AccueilFragmentState]'tir:


package client.android.fragments.state;

import android.widget.ArrayAdapter;
import client.android.architecture.custom.CoreState;
import client.android.dao.entities.CreneauMedecinJour;

public class AccueilFragmentState extends CoreState {

  // [Accueil] parçacığının durumu
  // seçilen doktorun konumu
  private int selectedMedecinPosition;
  // seçilen tarih
  private int year;
  private int month;
  private int dayOfMonth;
  // doktor seçici menüsünün veri kaynağı
  private String[] spinnerMedecinsDataSource;

  // yapıcılar
  public AccueilFragmentState() {

  }

  // getter ve setter'lar
...
}
  • 11. satır: doktorlar listesinden seçilen öğeyi döndürür;
  • 13-15. satırlar: takvimden seçilen tarihi döndürür;
  • 17. satır: doktor listesinin veri kaynağını döndürür;

Parçanın yaşam döngüsü şu şekilde uygulanır:


// üst sınıf yöntemlerinin uygulaması -------------------------------------
  @Override
  public CoreState saveFragment() {
    // görünüm kaydediliyor
    AccueilFragmentState state = new AccueilFragmentState();
    state.setSelectedMedecinPosition(spinnerMedecins.getSelectedItemPosition());
    state.setDayOfMonth(edtJourRv.getDayOfMonth());
    state.setMonth(edtJourRv.getMonth());
    state.setYear(edtJourRv.getYear());
    state.setSpinnerMedecinsDataSource(spinnerMedecinsDataSource);
    return state;
  }

  @Override
  protected int getNumView() {
    return IMainActivity.VUE_ACCUEIL;
  }

  @Override
  protected void initFragment(CoreState previousState) {
    // oturumdaki doktorları alıyoruz
    medecins = session.getMédecins();
    // İlk ziyaret mi?
    if (previousState == null) {
      // spinner tarafından görüntülenen tablo oluşturuluyor
      spinnerMedecinsDataSource = new String[medecins.size()];
      int i = 0;
      for (Medecin medecin : medecins) {
        spinnerMedecinsDataSource[i] = String.format("%s %s %s", medecin.getTitre(), medecin.getPrenom(), medecin.getNom());
        i++;
      }
    } else {
      // ilk ziyaret değil
      AccueilFragmentState state = (AccueilFragmentState) previousState;
      spinnerMedecinsDataSource = state.getSpinnerMedecinsDataSource();
    }
    // takvim
    calendrier = Calendar.getInstance();
  }

  @Override
  protected void initView(CoreState previousState) {
    // doktorların spinner'ını çağrı kaynağına eşleştiriyoruz
    ArrayAdapter<String> dataAdapterMedecins = new ArrayAdapter<>(activity, android.R.layout.simple_spinner_item, spinnerMedecinsDataSource);
    dataAdapterMedecins.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinnerMedecins.setAdapter(dataAdapterMedecins);
    // takvimin başlangıç tarihi bugündür
    edtJourRv.setMinDate(calendrier.getTimeInMillis());
    // İlk ziyaret mi?
    if (previousState == null) {
      // menü
      initMenu();
    }
  }

  @Override
  protected void updateOnSubmit(CoreState previousState) {
    // menü
    initMenu();
  }

  @Override
  protected void updateOnRestore(CoreState previousState) {
    // şu anda oturumda olan durum geri yüklenir
    AccueilFragmentState state = (AccueilFragmentState) previousState;
    // doktor seçimi (spinner)
    spinnerMedecins.setSelection(state.getSelectedMedecinPosition());
    // takvim
    edtJourRv.updateDate(state.getYear(), state.getMonth(), state.getDayOfMonth());
  }

  @Override
  protected void notifyEndOfUpdates() {
  }

  @Override
  protected void notifyEndOfTasks(boolean runningTasksHaveBeenCanceled) {
    // tüm görevler tamamlandığında veya iptal edildiğinde çağrılır
    // menü durumu
    initMenu();
    // sonraki görünüm?
    if (!runningTasksHaveBeenCanceled) {
      mainActivity.navigateToView(IMainActivity.VUE_AGENDA, ISession.Action.SUBMIT);
    }
  }

  // özel yöntemler ------------------------------------------------
  private void initMenu() {
    // menü durumu
    setAllMenuOptionsStates(true);
    setMenuOptionsStates(new MenuItemState[]{new MenuItemState(R.id.actionAnnuler, false)});
  }
  • satır 2-9: üst sınıfı talep ettiğinde, parça aşağıdaki öğelerin durumunu kaydeder:
    • 6. satır: doktor listesinde seçilen konum;
    • satır 7-9: takvimde seçilen tarihin gün, ay ve yılı;
    • 10. satır: doktorlar için kullanılan döner menünün veri kaynağı;
  • satır 14-17: parçanın numarası [IMainActivity.VUE_ACCUEIL]'tir;
  • satır 19-39: parça ilk kez oluşturulduğunda (previousState==null) veya sonraki seferlerde yeniden oluşturulduğunda (previousState !=null) yürütülür;
    • satır 25-31: ilk ziyaret durumunda, doktorlar için dönen menünün veri kaynağı oluşturulur;
    • satır 33-35: sonraki ziyaretlerde, döner menünün veri kaynağı parçanın önceki durumundan alınır;
  • satır 41-54: parçaya bağlı görünüm ilk kez oluşturulduğunda (previousState==null) veya sonraki ziyaretlerde yeniden oluşturulduğunda (previousState !=null) yürütülür;
    • satır 50-53: İlk ziyaret için, [Annuler] eylemi olmadan menü görüntülenir (satır 88-92);
    • satır 43-48: İlk ziyaret olsun ya da olmasın, tüm ziyaretlerde doktorların döner menüsü kaynağına eşleştirilir (satır 44-46) ve takvimin en erken tarihi bugünün tarihine ayarlanır (satır 48);
  • satır 56-60: [SUBMIT] işlemiyle parçaya ulaşıldığında yürütülür. Bu durumda, [CONFIG] görünümünden gelinir. Menü başlangıç durumuna getirilir;
  • satır 62-70: [NAVIGATION] veya [RESTORE] işlemiyle parçaya ulaşıldığında yürütülür;
    • 67. satır: doktor seçme çarkı, en son seçilen doktora yeniden konumlandırılır;
    • satır 69: takvim, en son seçilen tarihe ayarlanır;
  • satır 72-74: önceki tüm güncellemeler yapıldığında yürütülür. Yapılacak başka bir şey kalmaz;
  • satır 76-85: tüm asenkron görevler tamamlandığında yürütülür;
    • satır 80: menü varsayılan durumuna geri getirilir;
    • satır 82-84: görevler normal şekilde tamamlandıysa bir sonraki görünüme geçilir, aksi takdirde aynı görünümde kalınır;

3.6.8. Takvim Görünümünün Yönetimi

3.6.8.1. Görünüm

Ana sayfa görünümü şöyledir:

Image

Görsel arayüzün öğeleri şunlardır:

Type
Nom
1
TextView
txtTitre2
2
ListView
lstCreneaux

3.6.8.2. Parça

Takvim görünümü, aşağıdaki [AgendaFragment] parçası tarafından yönetilir:

 

package client.android.fragments.behavior;

import android.util.Log;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import client.android.R;
import client.android.architecture.core.AbstractFragment;
import client.android.architecture.core.ISession;
import client.android.architecture.core.MenuItemState;
import client.android.architecture.custom.CoreState;
import client.android.architecture.custom.IMainActivity;
import client.android.dao.entities.AgendaMedecinJour;
import client.android.dao.entities.CreneauMedecinJour;
import client.android.dao.entities.Medecin;
import client.android.dao.entities.Rv;
import client.android.dao.service.Response;
import client.android.fragments.state.AgendaFragmentState;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.ViewById;
import rx.functions.Action1;

@EFragment(R.layout.agenda)
@OptionsMenu(R.menu.menu_agenda)
public class AgendaFragment extends AbstractFragment {

  // görsel arayüz öğeleri
  @ViewById(R.id.txt_titre2_agenda)
  protected TextView txtTitre2;
  @ViewById(R.id.listViewAgenda)
  protected ListView lstCreneaux;

  // parça tarafından görüntülenen takvim
  private AgendaMedecinJour agenda;
  // zaman aralıklarına ilişkin bilgiler ListView
  private int firstPosition;
  private int top;
  // randevu silindi mi, silinmedi mi
  private boolean rdvSupprimé;
  // Eklenen veya silinen zaman aralığının numarası
  private int numCréneau;

  // ekleme/silme işleminden sonra takvimin güncellenmesi
  private void updateAgenda() {
  ...
  }

...

  // üst sınıf yöntemlerinin uygulanması ------------------------------------------------------
  ...
}
  • 27. satır: Parça, aşağıdaki [menu_agenda] menüsüyle ilişkilendirilmiştir:
  

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      tools:context=".activity.MainActivity1">
  <item
    android:id="@+id/menuActions"
    app:showAsAction="ifRoom"
    android:title="@string/menuActions">
    <menu>
      <item
        android:id="@+id/actionAnnuler"
        android:title="@string/actionAnnuler"/>
      <item
        android:id="@+id/actionAgenda"
        android:title="@string/actionAgenda"/>
    </menu>
  </item>
  <item
    android:id="@+id/menuNavigation"
    app:showAsAction="ifRoom"
    android:title="@string/menuNavigation">
    <menu>
      <item
        android:id="@+id/navigationToConfig"
        android:title="@string/navigationToConfig"/>
      <item
        android:id="@+id/navigationToAccueil"
        android:title="@string/navigationToAccueil"/>
    </menu>
  </item>
</menu>
  • 32-35. satırlar: görsel arayüz öğeleri;
  • satır 37-45: yöntemlere ait genel veriler;

3.6.8.2.1. [updateAgenda] yöntemi

Takvimdeki zaman aralıkları listesinin (yeniden) oluşturulması, kodun çeşitli yerlerinde gereklidir. Bu işlem, aşağıdaki özel [updateAgenda] yönteminde faktörleştirilmiştir:


  // ekleme/silme işleminden sonra takvimin güncellenmesi
  private void updateAgenda() {
    // takvim zaman aralıklarının (yeniden) oluşturulması
    // takvim oturumda alınır ve parça alanında saklanır
    agenda = session.getAgenda();
    // zaman aralıklarının ListView dosyası yeniden oluşturulur
    ArrayAdapter<CreneauMedecinJour> adapter = new ListCreneauxAdapter(activity, R.layout.creneau_medecin,
      agenda.getCreneauxMedecinJour(), this);
    lstCreneaux.setAdapter(adapter);
    // ListView'in doğru konumuna geri dönülür
    lstCreneaux.setSelectionFromTop(firstPosition, top);
}
  • 5. satır: Takvim oturumdan alınır ve parçanın [agenda] alanına kaydedilir;
  • 7-9. satırlar: [ListView] bileşen adaptörü tanımlanır. Bu adaptör, hem [ListView]'in veri kaynağını hem de bu kaynağın her bir öğesinin görüntüleme şablonunu tanımlar. Bu adaptörü yakında ele alacağız;
  • 11. satır: Takvimin önceki konumuna geri dönülür. Aslında, günün zaman dilimlerinin sadece bir kısmı görünmektedir. Son zaman dilimine bir randevu eklenir veya silinirse, yukarıdaki kod sayfayı yenileyerek yeni takvimi gösterecektir. Bu yenileme işlemi sonucunda tekrar ilk zaman dilimine konumlandırılıyoruz, ki bu istenen bir durum değildir. 5. satır bu sorunu giderir. Bu çözümün açıklaması URL ve [http://stackoverflow.com/questions/3014089/maintain-save-restore-scroll-position-when-returning-to-a-listview]'te bulunabilir;

[ListCreneauxAdapter] sınıfı, [ListView]'in bir satırını tanımlamak için kullanılır:

Image

Yukarıda görüldüğü gibi, zaman diliminde randevu olup olmadığına göre görüntüleme farklılık göstermektedir. [ListCreneauxAdapter] sınıfının kodu şöyledir:


...

public class ListCreneauxAdapter extends ArrayAdapter<CreneauMedecinJour> {

    // zaman aralıkları tablosu
    private CreneauMedecinJour[] creneauxMedecinJour;
    // çalışma bağlamı
    private Context context;
    // zaman aralığı listesindeki bir satırın görüntüleme düzeninin kimliği
    private int layoutResourceId;
    // tıklama dinleyicisi
    private AgendaFragment vue;

    // oluşturucu
    public ListCreneauxAdapter(Context context, int layoutResourceId, CreneauMedecinJour[] creneauxMedecinJour,
            AgendaFragment vue) {
        super(context, layoutResourceId, creneauxMedecinJour);
        // bilgiler kaydedilir
        this.creneauxMedecinJour = creneauxMedecinJour;
        this.context = context;
        this.layoutResourceId = layoutResourceId;
        this.vue = vue;
        // zaman aralıkları tablosunu saat sırasına göre sıralama
        Arrays.sort(creneauxMedecinJour, new MyComparator());
    }

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
    ...
}

// zaman aralığı tablosunu sıralama
class MyComparator implements Comparator<CreneauMedecinJour> {
...
    }
}
  • 3. satır: [ListCreneauxAdapter] sınıfı, [ListView] için önceden tanımlanmış bir adaptörü genişletmelidir; burada [ArrayAdapter] sınıfı; adından da anlaşılacağı gibi, [ListView]'e bir nesne dizisi sağlar; burada nesnelerin türü [CreneauMedecinJour]'tir. Bu varlığın kodunu hatırlayalım:

public class CreneauMedecinJour implements Serializable {

    private static final long serialVersionUID = 1L;
    // alanlar
    private Creneau creneau;
    private Rv rv;
...  
}
  • [CreneauMedecinJour] sınıfı bir zaman aralığı (5. satır) ve olası bir randevu (6. satır) içerir; randevu yoksa null içerir;

[ListCreneauxAdapter] sınıfının koduna geri dönelim:

  • 15. satır: oluşturucu dört parametre alır:
    1. çalışmakta olan Android etkinliği,
    2. [ListView] dosyasının her bir öğesinin içeriğini tanımlayan XML dosyası,
    3. doktorun randevu saatleri tablosu,
    4. görünümün kendisi;
  • 24. satır: randevu zaman aralıkları tablosu, saatlere göre artan sırada sıralanmıştır;

[getView] yöntemi, [ListView] dosyasındaki bir satıra karşılık gelen görünümü oluşturmakla görevlidir. Bu görünüm üç öğeden oluşur:

 
Id
Type
Rôle
1
txtCreneau
TextView
créneau horaire
2
txtClient
TextView
le client
3
btnValider
TextView
lien pour ajouter / supprimer un rendez-vous

[getView] yönteminin kodu şöyledir:


@Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        // doğru zaman aralığına gidiliyor
        CreneauMedecinJour creneauMedecin = creneauxMedecinJour[position];
        // satır oluşturulur
        View row = ((Activity) context).getLayoutInflater().inflate(layoutResourceId, parent, false);
        // zaman aralığı
        TextView txtCreneau = (TextView) row.findViewById(R.id.txt_Creneau);
        txtCreneau.setText(String.format("%02d:%02d-%02d:%02d", creneauMedecin.getCreneau().getHdebut(), creneauMedecin
                .getCreneau().getMdebut(), creneauMedecin.getCreneau().getHfin(), creneauMedecin.getCreneau().getMfin()));
        // müşteri
        TextView txtClient = (TextView) row.findViewById(R.id.txt_Client);
        String text;
        if (creneauMedecin.getRv() != null) {
            Client client = creneauMedecin.getRv().getClient();
            text = String.format("%s %s %s", client.getTitre(), client.getPrenom(), client.getNom());
        } else {
            text = "";
        }
        txtClient.setText(text);
        // bağlantı
        final TextView btnValider = (TextView) row.findViewById(R.id.btn_Valider);
        if (creneauMedecin.getRv() == null) {
            // ekle
            btnValider.setText(R.string.btn_ajouter);
            btnValider.setTextColor(context.getResources().getColor(R.color.blue));
        } else {
            // sil
            btnValider.setText(R.string.btn_supprimer);
            btnValider.setTextColor(context.getResources().getColor(R.color.red));
        }
        // bağlantı dinleyicisi
        btnValider.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                // bilgileri takvim görünümüne aktar
                vue.doValider(position, btnValider.getText().toString());
            }
        });
        // satırı oluşturur
        return row;
    }
  • 2. satır: position, [ListView]'te oluşturulacak satır numarasıdır. Aynı zamanda [creneauxMedecinJour] tablosundaki zaman aralığı numarasıdır. Diğer iki parametre göz ardı edilir;
  • 4. satır: [ListView] tablosundaki satırda görüntülenecek zaman aralığı alınır;
  • 6. satır: Satır, XML tanımından oluşturulur
 

[creneau_medecin.xml] kodunun içeriği şöyledir:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/RelativeLayout1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/wheat" >

    <TextView
        android:id="@+id/txt_Creneau"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:layout_marginLeft="20dp"
        android:text="@string/txt_dummy" />

    <TextView
        android:id="@+id/txt_Client"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/txt_Creneau"
        android:layout_marginLeft="20dp"
        android:layout_toRightOf="@+id/txt_Creneau"
        android:text="@string/txt_dummy" />

    <TextView
        android:id="@+id/btn_Valider"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/txt_Client"
        android:layout_marginLeft="20dp"
        android:layout_toRightOf="@+id/txt_Client"
        android:text="@string/btn_valider"
        android:textColor="@color/blue" />

</RelativeLayout>
 
  • 8-10. satırlar: [1] zaman aralığı oluşturulur;
  • satır 12-20: [2] müşteri kimliği oluşturulur;
  • 23. satır: zaman aralığında randevu yoksa;
  • 25-26. satırlar: mavi renkli [Ajouter] bağlantısı oluşturulur;
  • 29-30. satırlar: aksi takdirde kırmızı renkli [Supprimer] bağlantısı oluşturulur;
  • satır 33-40: [Ajouter / Supprimer] bağlantısının türü ne olursa olsun, bağlantıya yapılan tıklamayı yönetecek olan, görünümün [doValider] yöntemidir. Yöntem iki argüman alır:
    1. tıklanan zaman aralığının numarası,
    2. tıklanan bağlantının metni;
  • satır 42: az önce oluşturduğumuz satırı döndürürüz.

Bağlantıları yönetenin, [AgendaFragment] parçasının [doValider] yöntemi olduğu unutulmamalıdır. Bu yöntem şöyledir:


  // bir bağlantıya tıklama [Ajouter / Supprimer]
  public void doValider(int numCréneau, String texte) {
    // işlem devam ediyor mu?
    if (numberOfRunningTasks != 0) {
      Toast.makeText(activity, "Une opération est en cours. Patientez ou Annulez...", Toast.LENGTH_SHORT).show();
      return;
    }
    // geri dönmek için kaydırma konumunu not ediyoruz
    // şunu okuyun: [http://stackoverflow.com/questions/3014089/maintain-save-restore-scroll-position-when-returning-to-a-listview]
    // 1. öğenin konumunun tamamen görünür olup olmadığı
    firstPosition = lstCreneaux.getFirstVisiblePosition();
    // bu öğenin ListView'in üst kenarına göre Y ofseti
    // gizlenmiş olabilecek kısmın yüksekliğini ölçer
    View v = lstCreneaux.getChildAt(0);
    top = (v == null) ? 0 : v.getTop();
    // tıklanan bölmenin numarası da kaydedilir
    this.numCréneau = numCréneau;
    // bağlantı metnine göre, aynı işlem yapılmaz
    if (texte.equals(getResources().getString(R.string.lnk_ajouter))) {
      doAjouter();
    } else {
      doSupprimer();
    }
}
  • [doValider] yöntemi iki bilgi alır:
    • tıklanan zaman aralığının numarası;
    • tıklanan bağlantının metni (Ekle / Sil);
  • 4-7. satırlar: Devam eden asenkron görevler varsa, [Supprimer / Ajouter] bağlantıları üzerine tıklama engellenir. Bu, kod yazımını kolaylaştıran bir tercihtir. Tartışmaya açıktır;
  • satır 11-15: ListView zaman aralıklarının bilgileri (firstPosition, top) fragmanın alanlarına kaydedilir; böylece özel yöntem [updateAgenda], aynı kaydırma konumuyla bunu yeniden oluşturabilir;
  • 17. satır: Tıklanan zaman diliminin numarası kaydedilir;
  • 19-23. satırlar: Tıklanan bağlantının metnine göre bir ekleme veya silme işlemi yapılır;

3.6.8.2.2. [doSupprimer] yöntemi

[doSupprimer] yöntemi, tıklanan zaman aralığındaki randevunun silinmesini sağlar:


// randevunun silinmesi
  private void doSupprimer() {
    // iki görevin tamamlanmasını bekleme
    beginWaiting(2);
    // Randevu arka planda siliniyor
    rdvSupprimé = false;
    // silinecek randevunun kimliği
    long idRv = agenda.getCreneauxMedecinJour()[numCréneau].getRv().getId();
    // asenkron görevle silme
    executeInBackground(mainActivity.supprimerRv(idRv), new Action1<Response<Rv>>() {

      @Override
      public void call(Response<Rv> responseRv) {
        // sonucun kullanılması
        consumeRv(responseRv);
      }
    });
  }

  // yanıtın kullanılması
  private void consumeRv(Response<Rv> responseRv) {
    // hata mı?
    if (responseRv.getStatus() != 0) {
      // mesaj
      showAlert(responseRv.getMessages());
      // iptal
      doAnnuler();
      // UI'e geri dönüş
      return;
    }
    // randevunun silindiği belirtilir
    rdvSupprimé = true;
    // en güncel takvim isteniyor
    executeInBackground(
      mainActivity.getAgendaMedecinJour(agenda.getMedecin().getId(), session.getDayRv()),
      new Action1<Response<AgendaMedecinJour>>() {

        @Override
        public void call(Response<AgendaMedecinJour> responseAgendaMedecinJour) {
          // yanıt işleniyor
          consumeAgenda(responseAgendaMedecinJour);
        }
      });
  }

  // bir takvimin kullanılması
  private void consumeAgenda(Response<AgendaMedecinJour> responseAgendaMedecinJour) {
    // hata mı?
    if (responseAgendaMedecinJour.getStatus() != 0) {
      // mesaj
      showAlert(responseAgendaMedecinJour.getMessages());
      // iptal
      doAnnuler();
      // UI'e geri dön
      return;
    }
    // takvimi oturuma ekleniyor
    session.setAgenda(responseAgendaMedecinJour.getBody());
    // görünümün takvimini güncelliyoruz
    updateAgenda();
  }
  • 4. satır: üst sınıfa iki asenkron görevin başlatılacağı bildirilir ve bu iki görevin tamamlanması beklenir;
  • 8. satır: Silinecek randevunun kimliği alınır. Sunucunun bu bilgiye ihtiyacı vardır;
  • 9-18. satırlar: Asenkron bir görev aracılığıyla randevunun silinmesini talep ederiz;
    • 10. satır: [executeInBackground] yöntemi iki parametre bekler:
      • 10. satır: Yürütülecek ve izlenecek işlem, [mainActivity.supprimerRv(idRv)] yöntemi tarafından sağlanır;
      • satır 10-17: ikinci parametre, T'nin gözlemlenen işlem tarafından döndürülen tür olduğu (burada [Response<Rv>]) bir [Action1<T>] türü örneğidir
    • 15. satır: Yanıt alındığında, bu yanıt 21. satırdaki [consumeRv] yöntemine aktarılır;
  • 21-44. satırlar: Asenkron görevden yanıt alındı. Bu yanıt işlenir;
  • 23-30. satırlar: Öncelikle sunucunun yanıtın [status] alanında bir hata bildirip bildirmediğine bakılır;
    • 25. satır: Hata varsa, sunucunun yanıtın [messages] alanında yer alan mesajları görüntülüyoruz;
    • 27. satır: tüm görevleri iptal ediyoruz;
    • 29. satır: Kullanıcı arayüzüne geri dönülür;
  • satır 32: hata yoksa, randevunun silindiği not edilir;
  • satır 34-43: Parça tarafından şu anda görüntülenen takvimdeki randevuyu basitçe silmek yerine, doktorun yeni takvimini istenir. Zira uygulama çok kullanıcılıdır ve diğer kullanıcılar da doktorun takvimini değiştirmiş olabilir. Dolayısıyla en güncel versiyona sahip olmak daha iyidir;
  • satır 34-43, 47-61: [AccueilFragment] parçasında yapılan işlem, bu sefer oturumdan alınan bilgilerle tekrarlanır;

[beginWaiting] yöntemi (4. satır) şu şekildedir:


  // bekleme başlıyor
  protected void beginWaiting(int numberOfRunningTasks) {
    // görevlerin başlatılması için hazırlık yapılır
    beginRunningTasks(numberOfRunningTasks);
    // düğmelerin ve menülerin durumu
    setAllMenuOptionsStates(false);
    setMenuOptionsStates(new MenuItemState[]{new MenuItemState(R.id.menuActions, true),new MenuItemState(R.id.actionAnnuler, true)});

}
  • 4. satır: üst göreve, [numberOfRunningTasks] görevlerini başlatacağımızı bildiririz;
  • 6. satır: menüdeki tüm seçenekler gizlenir;
  • 7. satır: Ardından [Actions/Annuler] seçeneğini görünür hale getirir;

3.6.8.2.3. [doAnnuler] yöntemi

[Annuler] menü seçeneğine tıklama, [doAnnuler] yöntemi tarafından yönetilir:


  @OptionsItem(R.id.actionAnnuler)
  protected void doAnnuler() {
    if (isDebugEnabled) {
      Log.d(className, "Annulation demandée");
    }
    // asenkron görevler iptal ediliyor
    cancelRunningTasks();
}
  • 7. satır: üst sınıftan asenkron görevleri iptal etmesi istenir;

3.6.8.2.4. [Retour à la configuration] menü seçeneği

[Retour à la configuration] menü seçeneğine tıklandığında işlem şu şekilde yönetilir:


  @OptionsItem(R.id.navigationToConfig)
  protected void navigationToConfig() {
    // yapılandırma görünümüne geçiliyor
    mainActivity.navigateToView(IMainActivity.VUE_CONFIG, ISession.Action.NAVIGATION);
}
  • 4. satır: [NAVIGATION] eylemiyle yapılandırma görünümüne geçilir. Bu, yapılandırma görünümünü bırakıldığı haliyle geri getirmek istendiği anlamına gelir;

3.6.8.2.5. [Retour à l'accueil] menü seçeneği

[Retour à l'accueil] menü seçeneğine tıklama da benzer şekilde işlenir:


  @OptionsItem(R.id.navigationToAccueil)
  protected void navigationToAccueil() {
    // ana sayfaya geçiliyor
    mainActivity.navigateToView(IMainActivity.VUE_ACCUEIL, ISession.Action.NAVIGATION);
}

3.6.8.3. Parça yaşam döngüsünün yönetimi

Parçanın durumu şu şekildedir: [AgendaFragmentState]:


package client.android.fragments.state;

import android.widget.ArrayAdapter;
import client.android.architecture.custom.CoreState;
import client.android.dao.entities.CreneauMedecinJour;

public class AgendaFragmentState extends CoreState {

  // görünüm başlığı
  private String titre;
  // ListView
  private int firstPosition;
  private int top;

  // yapıcılar
  public AgendaFragmentState() {

  }

  public AgendaFragmentState(String titre) {
    this.titre = titre;
  }

  // getter ve setter'lar
...
}
  • 10. satır: görünümün üst kısmında görüntülenen başlık;
  • 12-13. satırlar: doktorun randevu aralıklarına ilişkin ListView'in scrolling'ini döndürür;

Parçanın yaşam döngüsü şu şekilde uygulanır:


// üst sınıf yöntemlerinin uygulanması ------------------------------------------------------
  @Override
  public CoreState saveFragment() {
    // durum kaydetme
    AgendaFragmentState state = new AgendaFragmentState();
    state.setTitre(txtTitre2.getText().toString());
    // geri dönmek için kaydırma konumunu not ediyoruz
    // QZXW2HTMLBW2h0dHA6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMzAxNDA4OS9tYWludGFpbi1zYXZlLXJlc3RvcmUtc2Nyb dosyasını oku2xsLXBvc2l0aW9uLXdoZW4tcmV0dXJuaW5nLXRvLWEtbGlzdHZpZXddZQX
    // 1. öğenin konumunun tamamen görünür olup olmadığı
    firstPosition = lstCreneaux.getFirstVisiblePosition();
    // bu öğenin ListView'in üst kenarına göre Y ofseti
    // gizlenmiş olabilecek kısmın yüksekliğini ölçer
    View v = lstCreneaux.getChildAt(0);
    top = (v == null) ? 0 : v.getTop();
    // tüm bu veriler kaydedilir
    state.setTop(top);
    state.setFirstPosition(firstPosition);
    return state;
  }

  @Override
  protected int getNumView() {
    return IMainActivity.VUE_AGENDA;
  }

  @Override
  protected void initFragment(CoreState previousState) {
    // İlk ziyaret mi?
    if (previousState != null) {
      // ilk ziyaret değil
      AgendaFragmentState state = (AgendaFragmentState) previousState;
      // ve ListView'teki bilgiler
      firstPosition = state.getFirstPosition();
      top = state.getTop();
    }
  }

  @Override
  protected void initView(CoreState previousState) {
  }

  @Override
  protected void updateOnSubmit(CoreState previousState) {
    // takvim alınır
    agenda = session.getAgenda();
    // sayfa başlığı oluşturuluyor
    Medecin medecin = agenda.getMedecin();
    txtTitre2.setText(String.format("Rendez-vous de %s %s %s le %s", medecin.getTitre(), medecin.getPrenom(),
      medecin.getNom(), session.getJourRv()));
    // menü durumu
    initMenu();
  }

  @Override
  protected void updateOnRestore(CoreState previousState) {
    // sayfa başlığı yeniden oluşturuluyor
    AgendaFragmentState state = (AgendaFragmentState) previousState;
    txtTitre2.setText(state.getTitre());
  }

  @Override
  protected void notifyEndOfUpdates() {
    // zaman aralıkları listesi yeniden oluşturuluyor
    updateAgenda();
  }

  @Override
  protected void notifyEndOfTasks(boolean runningTasksHaveBeenCanceled) {
    // menü durumu
    initMenu();
    // İptal edildiğinde ancak randevu silinmişse yerel takvimi güncellemek gerekir
    if (runningTasksHaveBeenCanceled && rdvSupprimé) {
      // yerel takvimden randevu silinir (genel takvime erişilemedi)
      agenda.getCreneauxMedecinJour()[numCréneau].setRv(null);
      // görsel arayüz güncellenir
      updateAgenda();
    }
  }


  // özel yöntemler ------------------------------------------------
  private void initMenu() {
    // menü durumu
    setAllMenuOptionsStates(true);
    setMenuOptionsStates(new MenuItemState[]{new MenuItemState(R.id.actionAnnuler, false)});
  }
  • satır 2-19: üst sınıfı talep ettiğinde, parça aşağıdaki öğelerin durumunu kaydeder:
    • 6. satır: görünümün üst kısmında görüntülenen başlık;
    • 7-17. satırlar: ListView'in scrolling'ini yeniden oluşturmaya olanak sağlayacak bilgiler (top, firstPosition);
  • 21-24. satırlar: parça numarası [IMainActivity.VUE_AGENDA];
  • satır 26-35: parça ilk kez oluşturulduğunda (previousState==null) veya sonraki seferlerde yeniden oluşturulduğunda (previousState !=null) yürütülür;
    • satır 30-34: Eğer bu, parçanın ilk ziyareti değilse, ListView'ten scrolling'i yeniden oluşturmaya olanak sağlayacak bilgiler (top, firstPosition) alınır;
  • satır 38-40: Fragmana ait görünüm ilk kez oluşturulduğunda (previousState == null) veya sonraki seferlerde yeniden oluşturulduğunda (previousState != null) yürütülür. Burada yapılacak bir şey yoktur çünkü zaman aralıklarının ListView değeri, özel yöntem [updateAgenda] (satır 61-65) tarafından oluşturulacaktır;
  • satır 42-52: [SUBMIT] işlemiyle bu parçaya ulaşıldığında yürütülür. Bu durumda, [ACCUEIL] görünümünden gelinir;
    • satır 45: [AccueilFragment] tarafından oturuma eklenen ajanda alınır;
    • satır 47-49: görünümün başlığı oluşturulur;
    • randevu aralıklarına ait ListView, özel [updateAgenda] yöntemi ile oluşturulacaktır (satır 61-65);
  • satır 54-59: bir [NAVIGATION] veya [RESTORE] işlemiyle parçaya ulaşıldığında yürütülür;
    • satır 57-58: görünümün başlığı yeniden oluşturulur;
    • zaman aralıklarının ListView'i, özel yöntem [updateAgenda] (satır 61-65) tarafından oluşturulacaktır;
  • satır 72-74: önceki tüm güncellemeler tamamlandığında yürütülür. Parçaya nasıl ulaşılırsa ulaşılsın bu güncelleme gerekli olduğundan, zaman aralıklarının ListView'i güncellenir;
  • satır 67-77: tüm asenkron görevler tamamlandığında yürütülür;
    • 70. satır: menü varsayılan durumuna geri getirilir (82-86. satırlar);
    • 72. satır: İki asenkron görev vardı. İptal edilmesine rağmen ilk görevin (randevunun silinmesi) başarılı olup olmadığına bakılır;
    • 74. satır: Eğer başarılıysa, randevu yerel ajandadan silinir
    • 75. satır: randevunun ekrandaki görüntüsü güncellenir;

3.6.9. Randevu ekleme ekranının yönetimi

3.6.9.1. Görünüm

Randevu ekleme ekranı şu şekildedir:

Image

Görsel arayüzün öğeleri şunlardır:

Type
Nom
1
TextView
txtTitre2
2
Spinner
spinnerClients

3.6.9.2. Parça

Randevu ekleme ekranı, aşağıdaki [AjoutRvFragment] parçası tarafından yönetilir:

 

package client.android.fragments.behavior;

import android.util.Log;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import client.android.R;
import client.android.architecture.core.AbstractFragment;
import client.android.architecture.core.ISession;
import client.android.architecture.core.MenuItemState;
import client.android.architecture.custom.CoreState;
import client.android.architecture.custom.IMainActivity;
import client.android.dao.entities.*;
import client.android.dao.service.Response;
import client.android.fragments.state.AjoutRvFragmentState;
import org.androidannotations.annotations.EFragment;
import org.androidannotations.annotations.OptionsItem;
import org.androidannotations.annotations.OptionsMenu;
import org.androidannotations.annotations.ViewById;
import rx.functions.Action1;

import java.util.List;
import java.util.Locale;

@EFragment(R.layout.ajout_rv)
@OptionsMenu(R.menu.menu_ajout_rv)
public class AjoutRvFragment extends AbstractFragment {

  // görsel arayüz öğeleri
  @ViewById(R.id.spinnerClients)
  protected Spinner spinnerClients;
  @ViewById(R.id.txt_titre2_ajoutRv)
  protected TextView txtTitre2;

  // müşteriler
  private List<Client> clients;

  // yerel veriler
  private Creneau creneau;
  private Medecin medecin;
  private boolean rdvAjouté;
  private Rv rv;
  private String[] spinnerClientsDataSource;

  // sayfa doğrulama
  @OptionsItem(R.id.actionValider)
  protected void doValider() {
   ...
  }
...

  // üst sınıf yöntemlerinin uygulanması ----------------------------------
...
}
  • 26. satır: Parça, aşağıdaki [menu_ajout_rv] menüsüyle ilişkilendirilmiştir:
  

<menu xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      tools:context=".activity.MainActivity1">
  <item
    android:id="@+id/menuActions"
    app:showAsAction="ifRoom"
    android:title="@string/menuActions">
    <menu>
      <item
        android:id="@+id/actionValider"
        android:title="@string/actionValider"/>
      <item
        android:id="@+id/actionAnnuler"
        android:title="@string/actionAnnuler"/>
    </menu>
  </item>
  <item
    android:id="@+id/menuNavigation"
    app:showAsAction="ifRoom"
    android:title="@string/menuNavigation">
    <menu>
      <item
        android:id="@+id/navigationToConfig"
        android:title="@string/navigationToConfig"/>
      <item
        android:id="@+id/navigationToAccueil"
        android:title="@string/navigationToAccueil"/>
      <item
        android:id="@+id/navigationToAgenda"
        android:title="@string/navigationToAgenda"/>
    </menu>
  </item>
</menu>
  • 30-33. satırlar: görsel arayüz öğeleri;
  • satır 36: müşteri listesi;
  • 43. satır: Müşteri seçici için veri kaynağı;

[Valider] bağlantısına yapılan tıklama, aşağıdaki [doValider] yöntemi tarafından yönetilir:


  // müşteriler
  private List<Client> clients;

  // yerel veriler
  private Creneau creneau;
  private Medecin medecin;
  private boolean rdvAjouté;
  private Rv rv;
  private String[] spinnerClientsDataSource;
...
// sayfanın doğrulanması
  @OptionsItem(R.id.actionValider)
  protected void doValider() {
    // seçilen müşteri alınır
    Client client = clients.get(spinnerClients.getSelectedItemPosition());
    // 2 asenkron görevin bekleme süreci başlıyor
    beginWaiting(2);
    // RV ekleniyor
    rdvAjouté = false;
    executeInBackground(
      mainActivity.ajouterRv(session.getDayRv(), creneau.getId(), client.getId()),
      new Action1<Response<Rv>>() {

        @Override
        public void call(Response<Rv> responseRv) {
          // yanıtın işlenmesi
          consumeRv(responseRv);
        }
      });
  }

  // bir Response<Rv> nesnesinin işlenmesi
  void consumeRv(Response<Rv> responseRv) {
    // hata mı?
    if (responseRv.getStatus() != 0) {
      // mesaj
      showAlert(responseRv.getMessages());
      // iptal
      doAnnuler();
      // UI'e geri dönüş
      return;
    }
    // randevunun eklendiği not edilir
    rdvAjouté = true;
    // randevu kaydedilir
    this.rv = responseRv.getBody();
    // yeni ajanda isteniyor
    executeInBackground(mainActivity.getAgendaMedecinJour(session.getAgenda().getMedecin().getId(), session.getDayRv()), new Action1<Response<AgendaMedecinJour>>() {

      @Override
      public void call(Response<AgendaMedecinJour> responseAgendaMedecinJour) {
        // yanıt işlenir
        consumeAgenda(responseAgendaMedecinJour);
      }
    });
  }

  // Response<AgendaMedecinJour> nesnesinin işlenmesi
  private void consumeAgenda(Response<AgendaMedecinJour> responseAgendaMedecinJour) {
    // hata mı?
    if (responseAgendaMedecinJour.getStatus() != 0) {
      // mesaj
      showAlert(responseAgendaMedecinJour.getMessages());
      // iptal
      doAnnuler();
      // UI'e geri dönüş
      return;
    }
    // gündem oturuma ekleniyor
    session.setAgenda(responseAgendaMedecinJour.getBody());
}
  • 13. satır: [doValider] yöntemi başladığında, 2, 5, 6 ve 9 numaralı alanlar parça yaşam döngüsü sırasında başlatılmıştır. Bunun nasıl yapıldığını göreceğiz;
  • 15. satır: Müşteri seçici çarkında seçilen öğeye karşılık gelen [Client] varlığı alınır;
  • 17. satır: üst sınıfa iki asenkron görev başlatılacağı bildirilir ve bekleme hazırlığı yapılır;
  • 19. satır: Başlangıçta randevu henüz doktorun takvimine eklenmemiştir;
  • satır 20-30: sunucuya bir randevu eklenmesi istenir;
    • 20. satır: [executeInBackground] yöntemi iki parametre bekler:
      • 20. satır: Yürütülecek ve gözlemlenecek işlem, [mainActivity.ajouterRv(session.getDayRv(), creneau.getId(), client.getId())] yöntemi tarafından sağlanır;
      • satır 22-29: ikinci parametre, T'nin gözlemlenen işlem tarafından döndürülen tür olduğu (burada [Response<Rv>]) bir [Action1<T>] türü örneğidir
    • 27. satır: Yanıt alındığında, bu yanıt 33. satırdaki [consumeRV] yöntemine aktarılır;
  • satır 33-56: sunucudan yanıt alındı. Bu yanıt işlenir;
    • satır 35-42: Öncelikle sunucunun yanıtın [status] alanında bir hata bildirip bildirmediğine bakılır;
    • 37. satır: Hata varsa, sunucunun yanıtın [messages] alanında yer alan mesajları görüntülüyoruz;
    • satır 39: tüm görevleri iptal ediyoruz;
    • satır 41 : kullanıcı arayüzüne geri dönülür;
    • satır 44 : hata yoksa, randevunun eklendiği not edilir;
    • satır 46: eklenen randevu, parçanın bir alanına kaydedilir;
    • satır 47-55: randevu silme işleminde olduğu gibi, randevu eklendikten sonra sunucudan doktorun en güncel takvimini istenir;
  • satır 47-56, 59-71: burada daha önce birkaç kez karşılaştığımız bir kod bulunmaktadır;

[beginWaiting] yöntemi (satır 17) şu şekildedir:


  // bekleme başlıyor
  protected void beginWaiting(int numberOfRunningTasks) {
    // görevlerin başlatılması için hazırlık yapılıyor
    beginRunningTasks(numberOfRunningTasks);
    // düğmelerin ve menülerin durumu
    setAllMenuOptionsStates(false);
    setMenuOptionsStates(new MenuItemState[]{new MenuItemState(R.id.menuActions, true),new MenuItemState(R.id.actionAnnuler, true)});

}
  • 4. satır: üst görevde, [numberOfRunningTasks] görevlerini başlatacağımız belirtilir;
  • 6. satır: menüdeki tüm seçenekler gizlenir;
  • 7. satır: Ardından [Actions/Annuler] seçeneğini görünür hale getirir;

[Annuler] menü seçeneğine tıklama, [doAnnuler] yöntemi tarafından yönetilir:


  @OptionsItem(R.id.actionAnnuler)
  protected void doAnnuler() {
    if (isDebugEnabled) {
      Log.d(className, "Annulation demandée");
    }
    // asenkron görevler iptal ediliyor
    cancelRunningTasks();
}
  • 7. satır: üst sınıftan asenkron görevleri iptal etmesi istenir;

Geri navigasyonlar aşağıdaki üç yöntemle sağlanır:


  @OptionsItem(R.id.navigationToConfig)
  protected void navigationToConfig() {
    // yapılandırma görünümüne geçiliyor
    mainActivity.navigateToView(IMainActivity.VUE_CONFIG, ISession.Action.NAVIGATION);
  }

  @OptionsItem(R.id.navigationToAccueil)
  protected void navigationToAccueil() {
    // yapılandırma görünümüne geçiliyor
    mainActivity.navigateToView(IMainActivity.VUE_ACCUEIL, ISession.Action.NAVIGATION);
  }

  @OptionsItem(R.id.navigationToAgenda)
  protected void navigationToAgenda() {
    // takvim görünümüne geçiliyor
    mainActivity.navigateToView(IMainActivity.VUE_AGENDA, ISession.Action.NAVIGATION);
}

3.6.9.3. Parça yaşam döngüsünün yönetimi

Parçanın durumu [AjoutRvFragmentState] şeklindedir:


package client.android.fragments.state;

import client.android.architecture.custom.CoreState;

// AjoutRvFragment parçasının durumu
public class AjoutRvFragmentState  extends CoreState {

  // seçili müşterinin konumu
  private int selectedClientPosition;
  // görünüm başlığı
  private String titre;
  // müşteri seçici çarkının veri kaynağı
  private String[] spinnerClientsDataSource;

  // alıcı ve ayarlayıcılar
...
}

Parçanın yaşam döngüsü şu şekilde uygulanır:


// üst sınıf yöntemlerinin uygulaması ----------------------------------
  @Override
  public CoreState saveFragment() {
    // görünümün kaydedilmesi
    AjoutRvFragmentState state = new AjoutRvFragmentState();
    state.setTitre(txtTitre2.getText().toString());
    state.setSelectedClientPosition(spinnerClients.getSelectedItemPosition());
    state.setSpinnerClientsDataSource(spinnerClientsDataSource);
    return state;
  }

  @Override
  protected int getNumView() {
    return IMainActivity.VUE_AJOUT_RV;
  }

  @Override
  protected void initFragment(CoreState previousState) {
    // oturumdaki müşteriler alınır
    clients = session.getClients();
    // İlk ziyaret mi?
    if (previousState == null) {
      // spinner tarafından görüntülenen tablo oluşturuluyor
      spinnerClientsDataSource = new String[clients.size()];
      int i = 0;
      for (Client client : clients) {
        spinnerClientsDataSource[i] = String.format("%s %s %s", client.getTitre(), client.getPrenom(), client.getNom());
        i++;
      }
    } else {
      // ilk ziyaret değil
      AjoutRvFragmentState state = (AjoutRvFragmentState) previousState;
      spinnerClientsDataSource = state.getSpinnerClientsDataSource();
    }
  }

  @Override
  protected void initView(CoreState previousState) {
    // spinner'ı veri kaynağına bağlama
    ArrayAdapter<String> dataAdapterClients = new ArrayAdapter<>(activity, android.R.layout.simple_spinner_item,
      spinnerClientsDataSource);
    dataAdapterClients.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    spinnerClients.setAdapter(dataAdapterClients);
    // İlk ziyaret mi?
    if (previousState == null) {
      // menü
      initMenu();
    }
  }

  @Override
  protected void updateOnSubmit(CoreState previousState) {
    // oturumda rezerve edilecek randevu numarası alınır
    int position = session.getPosition();
    // oturumda doktorun randevu takvimini alıyoruz
    AgendaMedecinJour agenda = session.getAgenda();
    // Randevu verilecek doktor ve zaman aralığı alınır
    medecin = agenda.getMedecin();
    creneau = agenda.getCreneauxMedecinJour()[position].getCreneau();
    // sayfanın 2. başlığını oluşturur
    String jour = session.getJourRv();
    txtTitre2.setText(String.format(Locale.FRANCE,
      "Prise de rendez-vous de %s %s %s le %s pour le créneau %02d:%02d-%02d:%02d", medecin.getTitre(),
      medecin.getPrenom(), medecin.getNom(), jour, creneau.getHdebut(), creneau.getMdebut(), creneau.getHfin(),
      creneau.getMfin()));
    // müşteri seçimi
    spinnerClients.setSelection(0);
    // menü
    initMenu();
  }

  @Override
  protected void updateOnRestore(CoreState previousState) {
    // önceki duruma geri dönülür
    AjoutRvFragmentState state = (AjoutRvFragmentState) previousState;
    // başlık
    txtTitre2.setText(state.getTitre());
    // yükleme çarkı
    spinnerClients.setSelection(state.getSelectedClientPosition());
  }

  @Override
  protected void notifyEndOfUpdates() {
  }

  @Override
  protected void notifyEndOfTasks(boolean runningTasksHaveBeenCanceled) {
    // menü durumu
    initMenu();
    // sonraki görünüm?
    if (!runningTasksHaveBeenCanceled) {
      mainActivity.navigateToView(IMainActivity.VUE_AGENDA, ISession.Action.SUBMIT);
      return;
    }
    // iptal edildi - randevu zaten eklendi mi?
    if (rdvAjouté) {
      // yerel takvim değiştiriliyor (genel takvim alınamadı)
      AgendaMedecinJour agenda = session.getAgenda();
      agenda.getCreneauxMedecinJour()[session.getPosition()].setRv(rv);
      // takvim görüntüleniyor
      mainActivity.navigateToView(IMainActivity.VUE_AGENDA, ISession.Action.SUBMIT);
      return;
    }
  }

  // özel yöntemler -------------------
  private void initMenu() {
    // menü durumu
    setAllMenuOptionsStates(true);
    setMenuOptionsStates(new MenuItemState[]{new MenuItemState(R.id.actionAnnuler, false)});
  }

  • 2-10. satırlar: üst sınıfı talep ettiğinde, fragman aşağıdaki öğelerin durumunu kaydeder:
    • 6. satır: görünümün üst kısmındaki başlık;
    • 7. satır: müşteri spinner'ında seçili öğenin konumu;
    • satır 8: müşteri seçici çarkının veri kaynağı;
  • satır 12-15: parçanın numarası [IMainActivity.VUE_AJOUT_RV]'tir;
  • satır 17-35: fragman ilk kez oluşturulduğunda (previousState==null) veya sonraki seferlerde yeniden oluşturulduğunda (previousState !=null) yürütülür;
    • 20. satır: oturumdaki müşteri listesi alınarak parçanın bir alanına yerleştirilir;
    • 22-30. satırlar: İlk ziyaret durumunda, müşteri döner menüsünün veri kaynağı oluşturulur;
    • satır 32-33: diğer ziyaretlerde, müşteri spinner'ının veri kaynağı parçanın önceki durumundan alınır;
  • satır 37-49: parçaya bağlı görünüm ilk kez oluşturulduğunda (previousState==null) veya sonraki seferlerde yeniden oluşturulduğunda (previousState !=null) yürütülür;
    • satır 40-43: her durumda, müşteri döner menüsü veri kaynağına eşleştirilir;
    • satır 45-48: İlk ziyaret için, [Annuler] eylemi olmadan menü görüntülenir (satır 107-111);
  • satır 51-70: [SUBMIT] işlemiyle parçaya ulaşıldığında yürütülür. Bu durumda, [AGENDA] görünümünden gelinir;
    • satır 54: randevunun ekleneceği zaman aralığının numarası alınır;
    • satır 56-59: Bu randevuyu eklemek için gerekli olan [Medecin] ve [Creneau] varlıklarını alınır ve fragmanın alanlarına yerleştirilir;
    • satır 61-65: Bu bilgilerle, görünümün başlığını oluşturabiliriz;
    • satır 67: müşteri seçici, ilk öğesine ayarlanır;
    • 69. satır: Menü, başlangıç durumuna getirilir ([Annuler] seçeneği olmadan);
  • 72-80. satırlar: [NAVIGATION] veya [RESTORE] işlemiyle parçaya ulaşıldığında yürütülür;
    • 77. satır: görünümün başlığı yeniden oluşturulur;
    • satır 79: müşteri seçici, en son seçilen müşteriye yeniden konumlandırılır;
  • satır 82-84: önceki tüm güncellemeler yapıldığında yürütülür. Burada yapılacak başka bir şey yoktur;
  • satır 86-104: tüm asenkron görevler tamamlandığında yürütülür;
    • satır 89: menü varsayılan durumuna geri getirilir;
    • 91-94. satırlar: Görevler normal şekilde tamamlandıysa, [SUBMIT] aracılığıyla [AGENDA] görünümüne geri dönülür (burada, NAVIGATION türünde bir eylem de olabilirdi);
    • satır 96-103: görevler iptal edilerek tamamlanmışsa, yine de randevunun eklenip eklenmediğine bakılır (bu, yeni ajandanın alınmasında hata olduğu anlamına gelir);
    • 98-99. satırlar: randevu eklenmişse;
      • 98-99. satırlar: sunucu tarafından döndürülen randevu, oturumdaki mevcut takvime eklenir;
      • satır 101: bir [SUBMIT] komutuyla [AGENDA] görünümüne geri dönülür (burada, bu işlem aynı zamanda NAVIGATION türünde bir eylem de olabilirdi);

3.7. Exécution

Aşağıdaki testleri yapın:

  • uygulamayı normal koşullarda kullanın ve çalıştığını kontrol edin;
  • Her bir görünüm için aygıtı döndürün ve her birinin doğru şekilde geri yüklendiğini kontrol edin;
  • [IMainActivity]'te birkaç saniyelik bir bekleme süresi ayarlayın;
  • ardından görevleri iptal edin ve elde edilen sonucun beklenen sonuç olup olmadığını kontrol edin;
  • bekleme süreleri sırasında aygıtı çalıştırın ve görevlerin düzgün bir şekilde iptal edildiğini ve herhangi bir çökme olmadığını kontrol edin;
  • [IMainActivity]'te parçaların sıralamasını değiştirin ve uygulamanın çalışmaya devam ettiğini kontrol edin;