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ü

Doktor seçimi ve randevu tarihi ekranı

Randevu saat aralığı seçimi ekranı

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

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

İ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:
[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

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:
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:
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());
}
}
- 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:
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:
3.5.4. Müşteri listesi
| |
|
Örnek:
3.5.5. Bir doktorun randevu saatleri listesi
|
- [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:
3.5.6. Bir doktorun randevu listesi
|
- [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:
3.5.7. Bir doktorun randevu takvimi
|
- [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:
|
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
|
- [idMedecin]: doktorun kimlik numarası;
Örnek 1:
Örnek 2:
3.5.9. Kullanıcı kimliği ile müşteri bilgilerini alma
|
- [idClient]: müşteri kimlik numarası;
Örnek 1:
Örnek 2:
3.5.10. Kimlik numarasına göre bir zaman aralığı bulma
|
- [idCreneau]: zaman aralığının kimlik numarası;
Örnek 1:
Cevapta, randevu saatinin sahibi olan doktorun adı yer almıyor, sadece kimlik numarası bulunuyor.
Örnek 2:
3.5.11. Kullanıcı kimliği ile randevu alma
|
- [idRv]: randevu kimlik numarası;
Örnek 1:
Cevapta ne müşterinin adı ne de randevu saati belirtilmiştir; sadece kimlik numaraları yer almaktadır.
Örnek 2:
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.

- [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:
|
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:
209 numaralı randevu, [status=0] nedeniyle iptal edilmiştir.
Örnek 2:
3.6. Android istemcisi

[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);
- 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:

Görsel arayüzün öğeleri şunlardır:
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:

Görsel arayüzün öğeleri şunlardır:
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;
- satır 15: [executeInBackground] yöntemi iki parametre bekler:
- 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:

Görsel arayüzün öğeleri şunlardır:
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:

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:
- çalışmakta olan Android etkinliği,
- [ListView] dosyasının her bir öğesinin içeriğini tanımlayan XML dosyası,
- doktorun randevu saatleri tablosu,
- 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:
[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:
- tıklanan zaman aralığının numarası,
- 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;
- 10. satır: [executeInBackground] yöntemi iki parametre bekler:
- 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:

Görsel arayüzün öğeleri şunlardır:
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;
- 20. satır: [executeInBackground] yöntemi iki parametre bekler:
- 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;












































