Android :: Ksoap2 Kullanarak Webservislere Erişme

İçerik: Android üzerinden webservislere erişim, gelen yanıtın parse edilmesi ve kullanılması.
Bu seferki yazım bir yıldan fazla zamandır sıkça kullandığım, Android üzerinden SOAP [0] webservislerine bağlanma ve kullanma konusunda kolaylıklar sağlayan ksoap2 [1] hakkında olacak. E-mail doğrulaması yapan public bir webservisi kullanan örnek bir uygulama üzerinden gideceğim.

android-soap-webservice
Kullanacağım webservisi basit göründüğü için aşağıdaki olarak seçtim (google’da karşıma ilk çıkanlardan biri):
http://ws.cdyne.com/emailverify/Emailvernotestemail.asmx?op=VerifyEmail
Metodların tam listesi:
http://ws.cdyne.com/emailverify/Emailvernotestemail.asmx
Ve servis tanımlaması:
http://ws.cdyne.com/emailverify/Emailvernotestemail.asmx?WSDL

Kullanacağımız metoddan bahsedecek olursak, bir email ve lisans anahtarını parametre olarak alıyor, arka planda kontrolünü yapıyor ve işlem hakkında 4 farklı bilgiyi bize veriyor (ResponseText, ResponseCode, LastMailServer, GoodEmail). Yapmak istediğim uygulama da verdiğim email ve lisans anahtarı bilgilerini bu servise gönderen ve gelen yanıtı aldıktan sonra parse edip, her bir bilgiyi ekranda ayrı ayrı gösterecek.

Servisi test etmek için url’de bir arayüz var ama böyle bir arayüze sahip olmayan servisler için test aracı olarak soapUI [2] ya da SOAPSonar [3] kullanılabilir. SOAPSonar Personal Edition’da test ettiğimde gönderilen istek aşağıdaki gibi oldu:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
			   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
			   xmlns:s="http://www.w3.org/2001/XMLSchema"
			   xmlns:tns="http://ws.cdyne.com/">
  <soap:Body>
    <tns:VerifyEmail>
      <tns:email>oguzozkeroglu@hotmail.com</tns:email>
      <tns:LicenseKey>test</tns:LicenseKey>
    </tns:VerifyEmail>
  </soap:Body>
</soap:Envelope>

Aldığım yanıt ise aşağıdaki gibi:

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
			   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
			   xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <VerifyEmailResponse xmlns="http://ws.cdyne.com/">
      <VerifyEmailResult>
        <ResponseText>Verified Email Address</ResponseText>
        <ResponseCode>2</ResponseCode>
        <LastMailServer>mx2.hotmail.com</LastMailServer>
        <GoodEmail>true</GoodEmail>
      </VerifyEmailResult>
    </VerifyEmailResponse>
  </soap:Body>
</soap:Envelope>

Kullanmadığım mail adresimin verified olmasını görmek sevindirdi açıkçası :) Şimdi aynı işlemleri bir Android projesi ile yapalım ve gelen yanıtı anlamlı hale getirip XML taglari arasındaki bilgileri birbirinden ayırıp ekranda gösterelim.

Öncelikle Eclipse’te bir Android projesi yaratalım. Kullanacağımız hiçbir metod ve özellik android-4 üzeri bir sürüm gerektirmiyor, o yüzden minimum ve target sdk seçenekleri 1.6 tutulabilir (en yenisi olsun diye kasmaya gerek yok :)

Yapacağımız ilk iş Manifest dosyasına internete erişim iznini eklemek olursa sonradan gerçekleşmesi muhtemel bir hatayı (java.net.SocketException) en baştan önlemiş oluruz. Eğer uygulamamız internet ile iletişime geçecekse aşağıdaki izni Manifest dosyamıza eklemiş olmamız gerekiyor.

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

Şimdi de gelen yanıttaki bilgileri tutacağımız nesne için bir class yazalım. Gelen XML’deki ada göre bir isimlendirme yaptım, o kısım için daha kolay akılda kalacak ya da daha anlamlı bir isim seçilebilir.

public class VerifyEmailResult {
	private String responseText;
	private int responseCode;
	private String lastMailServer;
	private boolean goodEmail;
	
	public VerifyEmailResult() {
		super();
	}

	public VerifyEmailResult(String responseText, int responseCode,
			String lastMailServer, boolean goodEmail) {
		super();
		this.responseText = responseText;
		this.responseCode = responseCode;
		this.lastMailServer = lastMailServer;
		this.goodEmail = goodEmail;
	}

	public String getResponseText() {
		return responseText;
	}

	public void setResponseText(String responseText) {
		this.responseText = responseText;
	}

	public int getResponseCode() {
		return responseCode;
	}

	public void setResponseCode(int responseCode) {
		this.responseCode = responseCode;
	}

	public String getLastMailServer() {
		return lastMailServer;
	}

	public void setLastMailServer(String lastMailServer) {
		this.lastMailServer = lastMailServer;
	}

	public boolean isGoodEmail() {
		return goodEmail;
	}

	public void setGoodEmail(boolean goodEmail) {
		this.goodEmail = goodEmail;
	}

	@Override
	public String toString() {
		return "VerifyEmailResult [responseText=" + responseText
				+ ", responseCode=" + responseCode + ", lastMailServer="
				+ lastMailServer + ", goodEmail=" + goodEmail + "]";
	}
}

Daha sonra bu classtan oluşturduğumuz nesnenin içini dolduracak, aslında yapmamız gereken işin çoğunu yapacak (webservis ile bağlantı kurup, parametrelerimizi karşıya gönderecek, ordan yanıt bekleyip aldığı yanıtı parse edip sonrasında bilgileri istediğimiz formatta bize verecek) olan classı yazalım.

import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;

public class EmailValidator {
	private static final String METHOD_NAME = "VerifyEmail";
	private static final String NAMESPACE = "http://ws.cdyne.com/";
	private static final String SOAP_ACTION = "http://ws.cdyne.com/VerifyEmail";
	private static final String URL = "http://ws.cdyne.com/emailverify/Emailvernotestemail.asmx";
	
	public static VerifyEmailResult verifyEmail (String eMail, String licenseKey) {
		VerifyEmailResult object = new VerifyEmailResult();
		
		SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
		request.addProperty("email", eMail);
		request.addProperty("LicenseKey", licenseKey);
		
		SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
		envelope.dotNet = true;
		envelope.setOutputSoapObject(request);
		
		HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
		androidHttpTransport.debug = true;
		
		try {
			androidHttpTransport.call(SOAP_ACTION, envelope);
			SoapObject response = (SoapObject) envelope.getResponse();
			
			if (response.hasProperty("ResponseText")) {
				if (response.getPropertyAsString("ResponseText") == null) {
					object.setResponseText(null);
				} else {
					object.setResponseText(response.getPropertyAsString("ResponseText"));
				}
			}
			
			if (response.hasProperty("ResponseCode")) {
				if (response.getPropertyAsString("ResponseCode") == null) {
					object.setResponseCode(-1);
				} else {
					object.setResponseCode(Integer.parseInt(response.getPropertyAsString("ResponseCode")));
				}
			}
			
			if (response.hasProperty("LastMailServer")) {
				if (response.getPropertyAsString("LastMailServer") == null) {
					object.setLastMailServer(null);
				} else {
					object.setLastMailServer(response.getPropertyAsString("LastMailServer"));
				}
			}
			
			if (response.hasProperty("GoodEmail")) {
				if (response.getPropertyAsString("GoodEmail") == null) {
					object.setGoodEmail(false);
				} else {
					object.setGoodEmail(Boolean.parseBoolean(response.getPropertyAsString("GoodEmail")));
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return object;
	}
}

Bu classı yazdığımızda IDE org.ksoap2.* importlarını yapamadığı için uyarı verecektir çünkü standart Java ve Android SDK içinde o classlar bulunmamaktadır. Peki şimdi ne yapmak gerekir? Yazının konusu olan ksoap2 kütüphanesini indirip [4] projemize eklemeliyiz. Benim indirip kullandığım dosyanın ismi: ksoap2-android-assembly-2.6.0-jar-with-dependencies.jar. JAR dosyasını projeme eklemek için de kullandığım yöntem: Proje klasörü içinde libs isminde bir klasör oluşturup projeye eklemek istediğim jar dosyalarını oraya attıktan sonra Eclipse Package Explorer’da proje üzerinde sağ click -> Properties -> Java Build Path -> Libraries -> Add JARs -> sonrasında da klasörün içindeki istenilen JAR dosyalarını eklenmesi..

Bu işlemler sıkıntısız şekilde tamamlandıktan sonra projede herhangi bir hata kalmaması gerekiyor.

Şimdi yukardaki kodda ne yaptığımıza gelelim.

	private static final String METHOD_NAME = "VerifyEmail";
	private static final String NAMESPACE = "http://ws.cdyne.com/";
	private static final String SOAP_ACTION = "http://ws.cdyne.com/VerifyEmail";
	private static final String URL = "http://ws.cdyne.com/emailverify/Emailvernotestemail.asmx";

Burda metod ismi, namespace, soap action ve servisin bulunduğu adresi belirttik.

VerifyEmailResult object = new VerifyEmailResult();

Parse edilen verileri tutacağımız geçici nesnemizi yarattık.

		SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
		request.addProperty("email", eMail);
		request.addProperty("LicenseKey", licenseKey);
		
		SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
		envelope.dotNet = true;
		envelope.setOutputSoapObject(request);
		
		HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
		androidHttpTransport.debug = true;

Burda da servise yapacağımız isteği oluşturup parametreleri ekliyoruz. Sonrasında SOAP envelope nesnesini oluşturup, bir .net servisini çağıracağımızı belirtiyoruz.

			androidHttpTransport.call(SOAP_ACTION, envelope);
			SoapObject response = (SoapObject) envelope.getResponse();

Burda servisi çağırıp gelen yanıtı SoapObject türünden bir nesneye atıyoruz.

XML’den farklı olarak ksoap2 nesneleri kendine özel bir formatta tutulur ve bunlar üzerinde işlem yapılır. Örneğin yukarıda XML halini yazmış olduğum isteğe karşılık gelen bizim request nesnemizin içeriği aşağıdaki gibi olacaktır:

VerifyEmail{
    email=oguzozkeroglu@hotmail.com;
    LicenseKey=test;
}

Gelen yanıta karşılık gelen response nesnemizin içeriği de aşağıdaki gibi olacaktır:

anyType{
    ResponseText=Verified Email Address;
    ResponseCode=2;
    LastMailServer=mx4.hotmail.com;
    GoodEmail=true;
}

Bu format parse işlemlerinde kolaylık sağlıyor. anyType XML’deki root’a yani VerifyEmailResult‘a karşılık geliyor gibi düşünebiliriz. Diğerleri zaten isminden anlaşılıyor. Herhangi bir özelliğe erişmek için

response.getPropertyAsString("OzellikIsmi");

kullanılabilir. (response.getProperty() ise geri bir String değil SoapObject döndüreceği için ekranda görüntülemede ya da nesnenin özelliklerini atamada sıkıntı çıkarabilir. Özelliğe o nesneden ulaşmak istediğimizde ise aşağıdaki gibi bir yapı kullanabiliriz.

String.valueOf(response.getProperty("OzellikIsmi"));

ksoap2’nin 2.5.8 sürümüne kadar böyle erişiliyordu. Parse işlemi için response nesnemizin öyle bir özelliği varsa ve null değilse kontrollerinden sonra atama yapıp en son nesneyi geri döndürüyoruz.

			if (response.hasProperty("ResponseText")) {
				if (response.getPropertyAsString("ResponseText") == null) {
					object.setResponseText(null);
				} else {
					object.setResponseText(response.getPropertyAsString("ResponseText"));
				}
			}
            /** ... */

Bu iki classı yazdıktan sonra ana sayfaya ait olan layout sayfasına (XML), bir adet Button ve 4 adet TextView ekledim. İşimiz tasarımla değil arka plandaki işlerle olduğu için tasarıma pek takılmayalım, dandik durduğunun farkındayım :)



Artık elimizdeki malzemeleri kullanmanın zamanı geldi. Ana sayfada aşağıdaki global tanımlamaları ekledim

	VerifyEmailResult object = null;
	final String eMail = "oguzozkeroglu@hotmail.com";
	final String licenseKey = "test";
    mThread thread = null;

Butonu tanımlayıp işlevsellik kazandıralım

		Button btnInvokeService = (Button) findViewById(R.id.btnInvoke);
		btnInvokeService.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				thread = new mThread();
				thread.start();
			}
		});

Butona basındığında tanımladığımız thread çalışacak. Peki thread ne yapacak?

		private class mThread extends Thread {
		@Override
		public void run() {
			object = EmailValidator.verifyEmail(eMail, licenseKey);
			handler.sendEmptyMessage(0);
		}

		private Handler handler = new Handler() {
			@Override
			public void handleMessage(Message msg) {
				thread.interrupt();
				setTexts();
			}
		};
	}

Çağırdığımızda EmailValidator classımızın verifyEmail metodunu çağıracak ve işlem bittikten sonra setTexts metodu çağrılacak. O metod ne yapacak?

	private void setTexts() {
		TextView tv1 = (TextView) findViewById(R.id.tv1);
		TextView tv2 = (TextView) findViewById(R.id.tv2);
		TextView tv3 = (TextView) findViewById(R.id.tv3);
		TextView tv4 = (TextView) findViewById(R.id.tv4);
		
		tv1.setText("ResponseText   ->" + object.getResponseText());
		tv2.setText("ResponseCode   ->" + object.getResponseCode());
		tv3.setText("LastMailServer ->" + object.getLastMailServer());
		tv4.setText("GoodEmail      ->" + object.isGoodEmail());
	}

Basit bir şekilde servisten dönen bilgileri oluşturduğumuz TextView‘lara atayacak ve ekranda görünmesini sağlayacak. Butona basıldıktan kısa bir süre sonra aşağıdakine benzer bir şeyler ortaya çıkmalı:



Orada görünen ResponseText ve ResponseCode listesinin tamamını metodlardan biri veriyor. Ben çıktıyı buraya da yazayım.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
			   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
			   xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <ReturnCodesResponse xmlns="http://ws.cdyne.com/">
      <ReturnCodesResult>
        <anyType xsi:type="xsd:string">0-Invalid Email Address</anyType>
        <anyType xsi:type="xsd:string">1-Not Used</anyType>
        <anyType xsi:type="xsd:string">2-Verified Email Address (Treat as a good email)</anyType>
        <anyType xsi:type="xsd:string">3-Mail Server will accept email (Treat as a good email)</anyType>
        <anyType xsi:type="xsd:string">4-User not found</anyType>
        <anyType xsi:type="xsd:string">5-Email Domain not found</anyType>
        <anyType xsi:type="xsd:string">6-Not Used</anyType>
        <anyType xsi:type="xsd:string">7-SMTP/Timeout Error (Treat as a good email)</anyType>
        <anyType xsi:type="xsd:string">8-Domain valid. Email server(s) down</anyType>
        <anyType xsi:type="xsd:string">9-License Key allowance exceeded</anyType>
      </ReturnCodesResult>
    </ReturnCodesResponse>
  </soap:Body>
</soap:Envelope>

Umarım yararlı bir yazı olmuştur. Kullandığım yöntemler bu işi yapmanın en iyi yöntemleri olmayabilir, araçlar ve kullandığım terimler doğru olmayabilir ama bu haliyle benim işimi gördüler bugüne kadar. Herhangi bir eleştiri / düzeltme / ekleme yapmak isteyen olursa konuya yorum bırakabilir ya da mail atabilir.


*0 -> http://en.wikipedia.org/wiki/SOAP
*1 -> http://code.google.com/p/ksoap2-android/
*2 -> http://www.soapui.org/
*3 -> http://www.crosschecknet.com/products/soapsonar.php
*4 -> http://code.google.com/p/ksoap2-android/wiki/HowToUse?tm=2

İlgili olabilecek yazılar:

Java %100 Nesne Yönelimli Bir Programlama Dili Midir?
Java :: Jersey ile RESTful Webservislere Eclipse, Maven ve Apache Tomcat Kullanarak Giriş Yapalım
EcaHack Hackathon @ Android Developer Days 2013 Ardından
Android :: JSON Parse İşlemleri – 2
Android :: JSON Parse İşlemleri – 1


Android :: Ksoap2 Kullanarak Webservislere Erişme” yazısına 32 yorum yapılmış.

  1. Merhaba,

    Uygulamada şöyle bir hata alıyorum:

    java.lang.RuntimeException: Cannot serialize: org.ksoap2.serialization.SoapSerializationEnvelope@44c15368

    Henüz çözümünü bulamadım. Elinizde bu uygulamanın çalışan bir örneği varsa kaynak kodları paylaşabilir misiniz?

    Teşekkürler

  2. Selamlar,
    main.xml dışında zaten tüm kodları paylaştım ki hatanın da oradaki kodlarla alakası olmadığı aşikar.

    Zannediyorum ki örnektekinden farklı bir webservis üzerinde çalışıyorsun. Aldığın hata genelde kompleks nesneleri, Double türünde ya da Date formatında verileri request’e eklemeye çalıştığında olur. ksoap2 maalesef ki şu anda o türleri direkt ekleyip gönderme imkanı sağlamıyor. Bunun için bazı farklı yöntemler kullanmak gerekiyor.

    Örneğin webservis tarafını da sen yazıyorsan, verileri String olarak alacak şekilde düzenleyebilirsin. Öyle bir şansın yoksa da Marshal Interface’ini gerçekleştirmen gerekir.

    Aşağıdaki adreste bununla ilgili bir döküman var, incelemeni tavsiye ederim.

    seesharpgears.blogspot.com/2010/11/implementing-ksoap-marshal-interface.html

    İyi çalışmalar.

  3. @soru
    Logcat çıktısını buraya yazabilir misin? Problemin ne olduğunu belki daha rahat anlarız.

    SOAP header eklemen gerekiyor olabilir ya da kullanmaya çalıştığın webservis bir authentication yapısı kullanıyor olabilir.

  4. Oğuz bey ben kendi yazdığım bir server a bağlanmak istiyorum. Serverı yazdım ve gui ile bağlantı sağladım.Ancak android tarafında bağlantıyı sağlayamadım.Sebebi sizce ne olabilir.
    HATA: NullPointerException

  5. @Ali
    NullPointerException’ın birçok sebebi olabilir. Daha ayrıntılı bir log üzerinden bakmak lazım.
    En azından hangi satırda hata veriyor onu yazabilir misiniz?

  6. response.hasProperty bu hata veriyor altı kırmızı cizgili hasProperty nasıl kullanılır ne yapmak gerek

  7. slmlar bende komplex yapıda gelen bir web service de çalışıyorum.Burdaki örnekte geri dönenn nesne içinteki tipler temel. Benim uygulamam da iç içe nesneler var temel tipe hemen erişemiyorum. Bu tip bi geri dönüş için ne yapmalıyım?

  8. @asil
    hasProperty(propertyName); metodu SoapObject’in (örnekteki response) String olarak verdiğiniz ‘propertyName’ isimli bir property’si varsa true, yoksa da false döndüren bir metod. Acaba verdiğiniz parametrenin türü String değil mi ya da parantez / noktalama işareti hatası yapmış olabilir misiniz?

  9. @sıla
    Eğer propertyler örnekteki gibi primitive type ise (en içteki property olarak düşünelim) o zaman

    repsonse.getPropertyAsString(propertyName);

    ile gelen veriyi String olarak alabiliyoruz. Fakat iç içe yapıda bir XML döndüğünde, en içte değilsek, property’lerin her birini SoapObject olarak alıp, onu yeniden parse etmeye çalışmak lazım. Bunu da SoapObject’e cast ederek yapabiliriz.

    SoapObject child = (SoapObject) response.getProperty(propertyName);

    Ya da propertyName yerine döngü içinde index ile erişebilirsiniz.

    SoapObject child = (SoapObject) response.getProperty(index);

    Bundan sonra da child nesnesini parse etmek kalıyor.

    String property = child.getPropertyAsString(propertyName);
    // ya da
    String property = child.getPropertyAsString(index);
    

    Eğer child da bir kompleks yapıdaysa onu da String değil, SoapObject olarak alıp parse etmek gerekebilir. Burda yapıyı recursive olarak ya da gelen yanıtın durumuna göre değişik şekillerde kurabilirsiniz.

  10. teşekkür ederim bende iç içe açmam gerektiğini düşünmüştüm,nasıl yapacağım konusunda fikir oldu iyi çalışmalar

  11. Sizin VerifyEmailResult classınızda yaptıgınız komplex tipi alt tiplerine ayırma işlemini de ben her bir komplex yapım için alttaki tipi için yapıcam demi doğru mu yorumlamışım?

  12. Merhaba, uygulamanızın aynısını yaptım ama “the application … has stopped unexpectedly.Please try again.” hatası alıyorum. Ne yaptıysam çözemedim. Yardımcı olabilir misiniz?

  13. @merve
    Logcat çıktısını yazabilir misiniz bir de ordan bakalım. Bu hatanın bir sürü farklı sebebi olabilir.

  14. oğuz bey ben de sizin gibi bir yapıda veri çekiyorum yalnız ben butona bastığımda sorgulamak için edittext’e girdiğim değeri almak istiyorum yardımcı olabilirseniz sevinirim

  15. @enes

    String str = et.getText().toString();

    ile EditText’e girilen değeri String olarak alabilir, sonrasında da servise parametre olarak gönderebilirsiniz.

  16. @merveyle aynı hatayı alıyorum. log cat çıktısı:
    01-19 13:41:48.757: E/dalvikvm(347): Unable to open stack trace file ‘/data/anr/traces.txt’: Permission denied.
    çok acil yardıma ihtiyacım var. lütfen yardım eder misiniz

  17. @enes
    Muhtemelen SD cart’a yazmak için Manifest dosyasına gerekli izni eklememişsiniz. Aşağıdaki satırı Manifest dosyasına ekleyip yeniden dener misiniz?

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  18. Hocam KSOAP2 ile ilgili yaklaşık 24 saattir tüm yazılmış örnekleri denedim neredeyse. Hiç sorunsuz çalışan tek örnek bu. Kesinlikle tek söyleyebileceğim şey; emeğine sağlık.

  19. ben de aynı hatayı alıyorum. logcat çıktısı şu şekilde:
    E/AndroidRuntime(364): at android.app.ActivityThread.main(ActivityThread.java:3683)

  20. Selam,
    Ksoap2 2.5.8 ile uygulama geliştiriyorum. Web servise insert işlemi yaparken Türkçe karakterde encode problemi var. Bu sorunu nasıl çözebilirim.
    Yardımlarınız için teşekkürler.

  21. Merhaba,

    MainActivity ‘e Thread sınıfını mı yazmamız gerekiyor ? Yada mainActivity de bulunması gereken nedir. Button içresinde ki Treadlar hata veriyor. Elinizde varsa bu projenin dosyasını yükleyebilirmisiniz.

  22. @Ahmet
    Evet Main Activity içinde Thread classından extend edilmiş bir inner class’a ihtiyaç var. Aslında Thread’den daha kullanışlı bir yöntem olan AsyncTask kullanılması daha doğru olabilir.

  23. Çok güzel bir yazı yazmışssın Oğuz tebrik ederim. Dönem ödevimi yapmada anlattıkların benim için çok faydalı oldu. Konunun felfesini ve çalışma mantığını çok iyi örneklemişsin.

  24. Merhaba kodda getPropertyAsString metodunda cannot resolve method getPropertyAsString hatası alıyorum. İnternetten araştırdım fakat sorunun kaynağını bulamadım. Yardımcı olabilirmisiniz ?

Bir Cevap Yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*