Archive for May, 2009

Objective-C’de Memory Problemlerinin Giderilmesi

iPhone’da uygulama geliştirken en sık karşılaştığımız problemlerinden biri memory management ( hafıza yönetimidir ). Geliştirdiğimiz kodda hafıza ile ilgili herhanbi bir kaçak varsa bu bize, hafıza uyarısı, herhangi bir anda beklenmedik  şekilde uygulamanın kapatılması şeklinde geri dönecektir. Bu hataların çoğunluğu derleme zamanında yakanlan(a)mayıp, çalışma zamanında (runtime) ortaya çıkarlar.

Bu hataların bir kısmı ( en güzel olanları :) ) ise simulator’de problem yaratmayıp , sadece telefon üzerinde çalışırken ortaya çıkanlarıdır.

Bu yazıda memory yönetimi , alloc , retain , release metodlari ne zaman/nasıl yapılmalı üzerinde değil, yapılan hatalar nasıl bulunup, fark edilebilir onların üzerinde durulacaktır.

Öncelikle, uygulamamız biz debug ederken-çalıştırırken , herhangi bir adımda EXC_BAD_ACCESS hatası alıp ta kapanıyorsa, ya da herhangi bir hata almadan beklenmedik şekilde kapanıyorsa , memory hatasından şüphelenmeliyiz. İlk yapmamız gereken Xcode > Run > Debugger menüsünden  uygulamamızın en son hangi işlemi yaparken hata aldığını bulmak olmalı.

Geliştirme ortamımızı (yani Xcode’u ) memory hatalarını anlayıp, bize uyarı verir hale getirmemiz gerekiyor. Bunun için

  • NSZombieEnabled
  • NSAutoreleaseFreedObjectCheckEnabled
  • NSDebugEnabled
  • MallocStackLoggingNoCompact
  • MallocStackLogging
  • MallocScribble
  • MallocGuardEdges
  • MallocPreScribble
parametrelerini set etmeliyiz.

Xcode > Project > Edit Active Executable Environment > Arguments tabından aşağıdaki değerleri environment variable bölümünde kısmında değerleri “YES” olacak şekilde set edin.

Bu değişkenler set edili iken iPhone üzerinde debug (device debug )işlemi yapamazsınız.

xcode3

Bu değişkenleri set ettiğimiz zaman uygulamamız herhangi bir anda çakıldığı zaman aşağıdaki şekilde  bize bir ipucu verecektir.

    debugger2

Uygulamaki bugların bulunması için bir static analyzer kullanabilirsiniz. Objective-C için en uygun aday LLVM/Clang Static Analyzer uygulaması Uygulamayı taz.bz2 formatında indirip açtıktan sonra  yapmanız gerekenler kısaca şöyle:

(Uygulamayı /Users/deniz/Downloads/checker-0.206 altına açtığınızı ve projenizin /Users/deniz/Projects/deneme altında olduğunu varsayalım)

Yeni bir terminal penceresi açıp .

  • export PATH=$PATH:/Users/deniz/Downloads/checker-0.206/
  • export PATH=$PATH:/Users/deniz/Downloads/checker-0.206/bin/
  • cd /Users/deniz/Projects/deneme
  • xcodebuild clean ( Bu komut yerine Xcode > Build > Clean kullanılabilir )
  • scan-build -o problemler -k xcodebuild -configuration Debug

En son olarak scan-build komutunun sonucunda belirtilen dizin için scan-view işlemi yapılır.

  • scan-view /Users/deniz/Projects/deneme/problemler/2009-05-18-1

Son komutun çıktısı aşağıdaki gibi bir ekran olacaktır:

result02

Bu ekranda yer alan View Report linkleriyle o anki hatanın neden kaynaklandığına bakılabilir.

    result13

    , ,

    2 Comments

    iPhone’a Test İçin Uygulama Nasıl Yüklenir

    Jailbreak yapılmamış iPhone’lara uygulama yüklemenin bilinen bir yöntemi App Store’dur. Fakat jailbreak yapmadan ya da App Store kullanmadan da uygulamalar, başka iPhone’lara yüklenebilir. Özellikle geliştiricilerin uygulamalarını test ettirebilmeleri için Apple tarafından sağlanan bu yöntem ile sertifika başına 100 adet cihaza kadar  uygulama yüklenebilmektedir.

    Apple her ne kadar bu konuda esneklik sağlasa da, bir yandan da test amacıyla uygulama yüklenecek cihazların seri numaralarını (UDID – Unique Device IDentifier) kayıt altına almaktadır. Bu yüzden cihazına test amacıyla, bir geliştiriceden alacağı uygulamayı yüklemek isteyenlerin sırasıyla aşağıdaki işlemleri yapması gerekmektedir.

    1) Uygulamanın yükleneceği cihazın UDID ’sinin tespit edilmesi.

    Bir cihazın UDID değerinin ne olduğunu bulmanın iki yöntemi vardır. Birincisi App Store’da yer alan  bu linkteki UDID isimli uygulamayı cihazımıza yükleyerek UDID değerini mail olarak almak.

    udid_app_01 udid_app_02

    UDID isimli uygulamayı çalıştırıp “Send UDID Now” seçeneğine tıkladığımızda, UDID değerini mail uygulamasına kopyalarak istediğimiz kişiye göndermemizi sağlıyor.

    İkinci yöntem ise cihazımızı USB kablosuyla bilgisayarımıza bağlayarak iTunes arayüzünden UDID değerini almak.

    udid_itunes_03

    Bu yöntemde, iTunes’ta sol taraftaki menüde cihazımıza tıklayarak Summary tabında cihazımıza ait bilgileri görebiliyoruz. Fakat bu ekranda ilk olarak Identifier (UDID) alanı yerinde Serial Number bilgisi gözüküyor. Bu alana bir kere tıkladığımızda ise yukarıdaki gibi UDID bilgisi ekrana çıkıyor. Ardından da Ctrl + C klavye kombinasyonunu kullanarak UDID bilgisini kopyalıyıyoruz.

    Yukarıdaki yöntemlerden herhangi birini kullanarak UDID ‘yi elde ettikten sonra da bu numarayı bize uygulamasını gönderecek olan geliştiriciye yolluyoruz.

    2) Provisioning dosyası ile birlikte uygulamanın iTunes üzerinden yüklenmesi

    UDID bilgimizi alan geliştirici bize, uygulama ile birlikte içinde bizim UDID bilgimizin bulunduğu .mobileprovision uzantılı bir dosya yollar. Bu dosya geliştiricinin yolladığı uygulamayı cihazımıza yüklememize izin veren dosyadır.

    Öncelikle yapmamız gereken geliştiricine bize yolladığı (Örnek: HelloWorld.zip) zip dosyası içerisinden .app uzantılı klasörü (Örnek: HelloWorld.app) çıkarmaktır. Ardından da iTunes’ta sol üst köşede LIBRARY altında yer alan Applications kısmına, .app ve .mobileprovision uzantılı dosyaları kopyalamaktır.

    itunes_apps_drag_drop

    Applications menüsü bizim App Store’dan indirdiğimiz dosyaların yer aldığı klasörü gösterir. Dolayısıyla artık test için bize yollanan bir uygulamayı sanki App Store’dan indrimişiz gibi cihazımızı Sync ederek yollayabiliriz.  (Not: Sync işlemi yapmadan evvel de sol menüde cihazımıza tıkladıktan sonra gelen Applications tabındaki transfer edeceğimiz uygulamamızı seçmeyi unutmuyoruz.)

    Eğer provisioning dosyasında UDID bilgimiz yoksa, ya da geliştirici uygulamasını bizim UDID’nin bulunduğu güncel provisioning dosyasını kullanarak imzalamadıysa aşağıdaki gibi hata almak muhtemeldir. Bazen de bilgisayarımızdaki iTunes’un azizliği nedeniyle bu hata oluşabilir, bu yüzden ilk hata aldığınızda hemen pes etmeyip Sync işlemini bir kez daha deneyin. Bu durumlarda uygulamanın geliştiricisinden yardım isteyiniz.

    1 Comment

    Android’le Adım Adım Uygulama Geliştirme – II

    Bu bölümde bir önceki bölümde geliştirmeye başladığımız uygulama üzerinde çalışmaya devam ederek liste içindeki değerlerin compound (birleşik) kontroller ile göze hoş gelen bir şekilde göstermeye ve web servis aracılığı ile aldığımız döviz değerleri cihaz üzerinde veritabanında saklamaya çalışacağız.

    Hatırlayacağınız gibi uygulamamızda döviz değerlerini ile gösterim yapacağımız ListView’ı ilişkilendirebilmek için ListView sınıfındaki “setAdaptor” metodunu kullanmış ve bu şekilde hazırladığımız bir ArrayAdaptor instance’ını view ile ilişkilendirmiştik.

    Adaptörler, Android platformunda View ve gösterilecek veri arasındaki ilişkiyi kuran sınıflardır. Adaptörler hem her parçanın kullanıcıya nasıl gösterileceğini belirtir hemde arkadaki veri ile gerekli bağlantıyı sağlarlar. Android platformunun kendi içerisinde gelen adaptörler arasında en çok aşağıdaki iki adaptör tanımı kullanılmaktadır:

    1- ArrayAdaptor: İçerisindeki her parça için “toString” metodunu sonucunu, tanımında verilen layout içerisindeki TextView ile ilişkilendirir
    2- SimpleCursorAdaptor: İçerik sağlayıcılar (content provider) üzerinde çalıştırılan sorgulama sonuçlarını, tanımında verilen layout içerisindeki Viewlar ile ilişkilendirir

    Bizde uygulamamızda döviz değerlerinin gösterimi için yeni bir compound kontrolü ve bunu kullanabilecek yapıya sahip bir ArrayAdaptor sınıfı geliştireceğiz.

    Öncelikle okuduğumuz döviz değerlerini tutabilecek yeni bir ara sınıf yazalım (hatırlarsanız daha önce bunun için String yapısını kullanmıştık):

    package com.yazarbozar.currencyexchange;
     
    public class CurrencyItem {
    	private String name;
    	private String buy;
    	private String sell;
     
    	public CurrencyItem(String name,String buy,String sell) {
    		this.name = name;
    		this.buy  = buy;
    		this.sell = sell;
    	}
     
    	public String getName() {
    		return name;
    	}
     
    	public void setName(String name) {
    		this.name = name;
    	}
     
    	public String getBuy() {
    		return buy;
    	}
     
    	public void setBuy(String buy) {
    		this.buy = buy;
    	}
     
    	public String getSell() {
    		return sell;
    	}
     
    	public void setSell(String sell) {
    		this.sell = sell;
    	}
    }

    Web servisten gelen döviz değerlerini okuduğumuz kod bloğunda (CurrencyExchangeHandler) bu yapıyı kullanalım. Ayrıca gelen değerler arasında “Tarih” bilgisinide ayıralım:

    .    private class CurrencyExchangeHandler extends DefaultHandler {
        	private final int 				STATE_DUMMY  = 0;
        	private final int 				STATE_DOVIZ  = 1;
        	private final int				STATE_ALIS   = 2;
        	private final int				STATE_SATIS  = 3;
     
        	private Vector<CurrencyItem>	currenyItemVector;
        	private int					state;
        	private StringBuilder 			processDate;
    	private String 				currencyName;
        	private String				currencyBuy;
        	private String				currencySell;
     
        	public CurrencyExchangeHandler(StringBuilder processDate,Vector<CurrencyItem> currenyItemVector) {
        		this.currenyItemVector = currenyItemVector;
        		this.processDate	        = processDate;
        		this.state		  	= STATE_DUMMY;
        	}
     
    		@Override
    		public void endElement(String uri, String localName, String name) throws SAXException {
    			if("DOVIZ".equalsIgnoreCase(localName)) {
    				if("Tarih".equalsIgnoreCase(currencyName)) {
    					try {
    						processDate.append(currencyBuy);						
    					} catch (Exception e) {
    						//do nothing
    					}
    				} else {
    					currenyItemVector.add(new CurrencyItem(currencyName,currencyBuy,currencySell));					
    				}
    			}
    			state = STATE_DUMMY;
    		}
     
    		@Override
    		public void startElement(String uri, String localName, String name,Attributes attributes) throws SAXException {
    			if("DOVIZ".equalsIgnoreCase(localName)) {
    				state = STATE_DOVIZ;
    			} else if("ALIS".equalsIgnoreCase(localName)) {
    				state = STATE_ALIS;
    			} else if("SATIS".equalsIgnoreCase(localName)) {
    				state = STATE_SATIS;
    			}
    		}
     
    		@Override
    		public void characters(char[] ch, int start, int length) throws SAXException {
    			switch (state) {
    				case STATE_DOVIZ:
    					currencyName = new String(ch, start, length);
    					break;
    				case STATE_ALIS:
    					currencyBuy = new String(ch, start, length);
    					break;
    				case STATE_SATIS:
    					currencySell = new String(ch, start, length);
    					break;
    			}
    		} 
        }

    Şimdi de CurrencyItem’ı ListView içerisinde gösterirken kullanacağımız compound kontrol yapısını hazırlayalım. Bunun için öncelikle bir layout tanımı yaratıp, Android platformunun sistem servislerinden olan “LayoutInflate” servisi ile kullanarak istediğimiz View yapısını elde edeceğiz.

    Layout’ta kullanacağımız renk değerlerini “/res/values/” dizini altında “colors.xml” isimli bir dosya yaratıp tanımlayalım (burada ayrıca ListView içinde uygun renk tanımını hazırlayalım, bunu ileride ön ve arka plan arasındaki renk uyumsuzluklarını gidermede kullanacağız):

    <resources>
        <color  name="list_back_color">#FFFF00</color>
        <color  name="currency_name_text_color">#000000</color>
        <color  name="currency_buy_text_color">#000000</color>
        <color  name="currency_sell_text_color">#000000</color>
        <color  name="currency_name_back_color">#FF3300</color>
        <color  name="currency_buy_back_color">#FF9900</color>
        <color  name="currency_sell_back_color">#FFFF00</color>
    </resources>

    Sonrasında layout tanımımızı oluşturalım (”/res/layout/currency_item.xml”):

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"
    >
    	<TextView 
    		android:id="@+id/currencyName"
    		android:layout_width="wrap_content"
    		android:layout_height="fill_parent"
    		android:width="60px"
    		android:textColor="@color/currency_name_text_color"
    		android:background="@color/currency_name_back_color"
    	/>	
    	<TextView
    		android:id="@+id/currencyBuy"
    		android:layout_width="wrap_content"
    		android:layout_height="fill_parent"
    		android:width="60px"
    		android:textColor="@color/currency_buy_text_color"
    		android:background="@color/currency_buy_back_color"
    	/>
    	<TextView
    		android:id="@+id/currencySell"
    		android:layout_width="fill_parent"
    		android:layout_height="fill_parent"
    		android:textColor="@color/currency_sell_text_color"
    		android:background="@color/currency_sell_back_color"
    	/>
    </LinearLayout>

    Burada fark ettiğiniz gibi “CurrencyName” ve “CurrencyBuy” alanları için sabit bir alan ayırırken, “CurrencySell” alanının geri kalan tüm bölgeyi (satır bazında) kaplamasını sağlıyoruz. Bu şekilde satırlar listelendiğinde sutünların düzgün bir şekilde sıralanmasını sağlamış olacağız.

    Uygulamamızda kullandığımız ListView’ı içeren layout yapısını açıp, ListView için hazırladığımız arkaplan renk kodunu kullanmasınıda sağlayalım:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >
    <ListView
        android:id="@+id/currencyList"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:background="@color/list_back_color"
        />
    </LinearLayout>

    Son olarak yukarıda yaptığımız tanımları destekleyen bir ArrayAdaptor yapısı oluşturmamız gerekecek. Bunun için ArrayAdaptor sınıfını extend eden yeni bir sınıf oluşturacağız. Ve bu sınıf içerisindeki “getView” metodunu override ederek bizim tasarladığımız View’ın kullanılmasını sağlayacağız.

    package com.yazarbozar.currencyexchange;
     
    import android.content.Context;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ArrayAdapter;
    import android.widget.LinearLayout;
    import android.widget.TextView;
     
    public class CurrencyItemAdapter extends ArrayAdapter<CurrencyItem> {
     
    	public CurrencyItemAdapter(Context context, int textViewResourceId, CurrencyItem[] objects) {
    		super(context, textViewResourceId, objects);
    	}
     
    	@Override
    	public View getView(int position, View convertView, ViewGroup parent) {
    		CurrencyItem currencyItem = getItem(position);
    		View 		 currencyItemView;
     
    		if(convertView == null) {
    			LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    			currencyItemView              = layoutInflater.inflate(R.layout.currency_item, null);
    		} else {
    			currencyItemView = convertView;
    		}
     
    		TextView nameView = (TextView) currencyItemView.findViewById(R.id.currencyName);
    		TextView buyView   = (TextView) currencyItemView.findViewById(R.id.currencyBuy);
    		TextView sellView   = (TextView) currencyItemView.findViewById(R.id.currencySell);
     
    		nameView.setText(currencyItem.getName());
    		buyView.setText(currencyItem.getBuy());
    		sellView.setText(currencyItem.getSell());
     
    		return currencyItemView;
    	}
    }

    Burada aşağıdaki kullanım dikkatinizi çekebilir:

    Burada öncelikle tekrar kullanım için verilen view’ı (convertView) inceliyoruz. Eğer bu view “null” ise bu durumda LayoutInflater servisine yaratmış olduğumuz layout tanımını parametre olarak vererek ona ait bir instance’ını yaratmış oluyoruz (layout’un bir View extension’ı olduğunu unutmayalım).

    Bundan sonra yapmamız gereken tek şey, ListView’ın bu hazırladığımız adaptörü kullanmasını sağlamak olacak:

        public CurrencyItem[] getExchange() throws IOException, SAXException, ParserConfigurationException {
        	Vector<CurrencyItem> currenyItemVector        = new Vector<CurrencyItem>();    
        	StringBuilder		 processDate		= new StringBuilder();
        	SAXParserFactory 	 saxParserFactory 	= SAXParserFactory.newInstance();
        	SAXParser		 	 saxParser		  	= saxParserFactory.newSAXParser();
     
        	saxParser.parse(getResources().getString(R.string.currency_url), new CurrencyExchangeHandler(processDate,currenyItemVector));
        	return currenyItemVector.toArray(new CurrencyItem[currenyItemVector.size()]);
        }

    Uygulamamızı çalıştırırsak:

    1

    Hatırlarsanız web servis sonucunda gelen tarih bilgisini SAX parse işlemi sırasında ayırmıştık, bunuda listemizin başına bir TextView ile ekleyelim.

    İlk önce kullanacağımız sabit değerleri “/res/values” altındaki ilgili dosyalarda tanımlayalım:
    strings.xml

        <string name="header_text">Currency Exchange Values on</string>

    colors.xml

        <color  name="header_text_color">#000000</color>
        <color  name="header_back_color">#FFFFFF</color>

    ListView’ın tanımlı olduğu layout dosyasına bu bilgiyi göstereceğimiz bir TextView ekleyelim:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >
    <TextView
        android:id="@+id/currencyHeader"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:textColor="@color/header_text_color"
        android:background="@color/header_back_color"
    	/>   
    <ListView
        android:id="@+id/currencyList"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:background="@color/list_back_color"
        />
    </LinearLayout>

    En son olarak ilgili Activity tanımında bu TextView’ın içeriğinin hazırlanmasını sağlayan kısmı ekleyelim:

    public class CurrencyExchangeActivity extends Activity {
    	private ListView	listView;
    	private TextView	headerView;
     
        public void onCreate(Bundle savedInstanceState) {
            ....
            listView 	= (ListView) findViewById(R.id.currencyList);
            headerView	= (TextView) findViewById(R.id.currencyHeader);
            ....
        }
     
        public CurrencyItem[] getExchange() throws IOException, SAXException, ParserConfigurationException {
        	Vector<CurrencyItem>  currenyItemVector       = new Vector<CurrencyItem>();    
        	StringBuilder		 processDate		= new StringBuilder();
        	SAXParserFactory 	 saxParserFactory 	= SAXParserFactory.newInstance();
        	SAXParser		 	 saxParser		  	= saxParserFactory.newSAXParser();
     
        	saxParser.parse(getResources().getString(R.string.currency_url), new CurrencyExchangeHandler(processDate,currenyItemVector));
        	headerView.setText(getResources().getText(R.string.header_text)+" "+processDate.toString());    	
        	return currenyItemVector.toArray(new CurrencyItem[currenyItemVector.size()]);
        }
        ....
    }

    Uygulamamızı tekrar çalıştırdığımızda:

    21

    Şimdi uygulamamız bünyesinde web servisten aldığımız değerleri cihaz üzerindeki veritabanında saklama işlemini inceleyelim.

    Android platformu bünyesinde gelen SQLite kütüphanesi bize uygulamamız içerisinde kullanabileceğimiz bir ilişkisel veritabanı yapısı (RDBMS) sunmaktadır. Bu kütüphane aracılığı ile normal uygulamalarımızda yaptığımız tüm veritabanı işlemlerini gerçekleştirebiliriz (unutmamaız gereken bir nokta, uygulama içerisinde yarattığımız veritabanı sadece o uygulamaya özgüdür, uygulamalar arasında paylaşım yapmak için Android bünyesindeki Content Provider yapısı kullanılmalıdır).

    Öncelikle veritabanı üzerinde yaratma, açma ve kapama işlemleri için çekirdek bir sınıf yazıp, sonrada bu sınıfın üzerine CRUD işlemleri için ayrı bir sınıf hazırlayacağız. Veritabanı üzerindeki yaratma, açma ve kapama işlemlerini içerecek sınıfımız “SQLiteOpenHelper” sınıfından türeyecek, ve bu sınıf içerisindeki onCreate ve onUpdate metodlarını kullanarak uygulamamıza özgü bir veritabanı yapısı hazırlayacak.

    public class CurrencyExchangeDBHelper extends SQLiteOpenHelper{
    	private String createSQL;
    	private String updateSQL;
     
    	public CurrencyExchangeDBHelper(Context context, String dbName,CursorFactory factory, int dbVersion, String createSQL, String updateSQL) {
    		super(context, dbName, factory, dbVersion);
    		this.createSQL = createSQL;
    		this.updateSQL = updateSQL;
    	}
     
    	@Override
    	public void onCreate(SQLiteDatabase db) {
    		db.execSQL(createSQL);
    	}
     
    	@Override
    	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    		db.execSQL(updateSQL);
    	}
    }

    “onCreate” metodu, belirtilen isimde bir veritabanı (uygulamaya özgü) bulunmadığında, “onUpgrade” metodu ise bulunan veritabanının versiyonu ile constructor’da verilen versiyonun eşit olmaması durumunda çalıştırılmaktadır. Artık bu sınıf üzerinde “getWritableDatabase” metodunu kullanarak CRUD işlemlerimizi gerçekleştirebileceğimiz bir SQLiteDatabase instance’ı oluşturabiliriz (”getReadableDatabase” metodu ile sadece okuma amaçlı SQLiteDatabase instance’ıda oluşturabiliriz). Aslında Android platformu, SQLiteOpenHelper dışında “Context.openOrCreateDatabase” metodu ile de veritabanı yaratmamıza izin vermektedir, ancak bu kullanımda veritabanı içindeki tablo yaratımları ve gerekli tablo güncellemelerini bizim kontrol etmemiz gerekmektedir.

    Sonra hazırladığımız bu sınıfı kullanan ve CRUD işlemlerini gerçekleştirebilmemiz için veritabanımıza referans hazırlayan bir sınıf hazırlayalım:

    public class CurrencyExchangeDBAdaptor {
    	private static CurrencyExchangeDBAdaptor instance; 
    	private static SimpleDateFormat			 dateFormatFrom = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss");
    	private static SimpleDateFormat			 dateFormatTo   = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
     
    	private SQLiteDatabase 				database;
    	private CurrencyExchangeDBHelper	currencyExchangeDBHelper;
    	private String		   				dbName    	      = "CurrencyExchangeDB";		 
    	private int							dbVersion	      = 1;
    	private String		   				tableName 	      = "CurrencyExchangeTable";		 
    	private String		   				dbKeyId	      = "id";
    	private String		   				dbKeyDate 	      = "date";	
    	private String		   				dbKeyCurrCode  = "code";	
    	private String		   				dbKeyCurrBuy    = "buy";	
    	private String		   				dbKeyCurrSell    = "sell";	
    	private String		   				createSQL        = "create table CurrencyExchangeTable(id integer primary key autoincrement, " +
    										   				" date date not null, code text not null, buy text not null, sell text not null)";
    	private String		   				updateSQL       = "";
     
    	private CurrencyExchangeDBAdaptor(Context context) {
     
    		currencyExchangeDBHelper = new CurrencyExchangeDBHelper(context,dbName,null,dbVersion,createSQL,updateSQL);
    		database                         = currencyExchangeDBHelper.getWritableDatabase();
    	}
     
    	public static CurrencyExchangeDBAdaptor getInstance(Context context) {
    		if(instance == null) {
    			instance = new CurrencyExchangeDBAdaptor(context);
    		}
    		return instance;
    	}
    }

    Fark ettiyseniz, yarattığımız tablo yapısında normal veri yapımızda bulunmayan “id” isimli bir alanda (unique index olarak) yarattık. Normal tablo yapılarında bu tarz bir alan zorunlu olmasada bu tarz bir yaratım tavsiye edilmektedir (Ayrıca ileride bu tabloyu Content Provider yapısı ile diğer uygulamalara açmak isterseniz bu tarz bir alan zorunludur). Ayrıca “Date” alanları içinde iki tane format instance’ı yarattık çünkü bize web servisten gelen tarih formatı “dd.MM.yyyy HH:mm:ss” şeklinde iken SQLite’ın desteklediği tarih formatı ise “yyyy-MM-dd HH:mm:ss” şeklindedir (SQLite’ın desteklediği diğer tarih formatları için : http://www.sqlite.org/cvstrac/wiki?p=DateAndTimeFunctions).

    CRUD işlemlemleri için SQLiteDatabase üzerindeki şu metodları kullanacağız:
    1- insert(String table, String nullColumnHack, ContentValues values)
    Burada özellikle “nullColumnHack” parametresi biraz kafa karıştırıcı olabilir, ContentValues olarak verilen yapı boş ise burada belirtilen sütun alanı “NULL” olarak atanır
    2- update(String table, ContentValues values, String whereClause, String[] whereArgs)
    3- delete(String table, String whereClause, String[] whereArgs)

    	public void insertCurrencyExchange(String date,CurrencyItem currencyItem) {
    		ContentValues values = new ContentValues();
    		values.put(dbKeyDate, date);
    		values.put(dbKeyCurrCode, currencyItem.getName());
    		values.put(dbKeyCurrBuy, currencyItem.getBuy());
    		values.put(dbKeyCurrSell, currencyItem.getSell());
     
    		database.insert(tableName, null, values);
    	}
     
    	public Cursor getCurrencyExchange(String date) {
    		return database.query(tableName, new String[]{dbKeyCurrCode,dbKeyCurrBuy,dbKeyCurrSell}, "date=?", new String[]{dateFormatTo.format(dateFormatFrom.parse(date))}, null, null, null, null);
    	}
     
    	public boolean isCurrencyExchangeExists(String date, String currencyCode) {
    		Cursor cursor = null;
    		try {
    			cursor = database.query(tableName, new String[]{dbKeyId}, "date=? and code=?", new String[]{dateFormatTo.format(dateFormatFrom.parse(date)),currencyCode}, null, null, null);
    			if(cursor.getCount() > 0) {
    				return true;
    			} else {
    				return false;
    			}
    		} finally {
    			cursor.close();
    		}
    	}

    Şimdi bu sınıfı, uygulamamız içerisinde iki temel amaç için kullanalım:
    1- Web servis aracılığı ile aldığımız döviz kur değerleri veritabanında bulunmuyorsa veritabanına yazalım
    2- Eğer varsa her döviz kur değerini en yakın zamandaki ilgili döviz kur değeri ile kıyaslayıp artma/azalma/aynı kalma durumlarını kullanıcıya gösterelim

    İlk işlem için

    public class CurrencyExchangeActivity extends Activity {
        ....
        public CurrencyItem[] getExchange() throws IOException, SAXException, ParserConfigurationException {
           ....
           for (CurrencyItem currencyItem : currenyItemVector) {
    		if(!CurrencyExchangeDBAdaptor.getInstance(this).isCurrencyExchangeExists(processDate.toString(), currencyItem.getName())) {
    			CurrencyExchangeDBAdaptor.getInstance(this).insertCurrencyExchange(processDate.toString(), currencyItem);
    		}
           }
           ....
        }
        ....
    }

    Uygulamamızı çalıştırdıktan sonra veritabanına yazılan kayıtları gözlemlemek için ADB (Android Debug Bridge) kullanılabilir. Bunun için:
    1- SDK kurulumunun altındaki adb.exe adlı dosya “adb shell” komutu ile çalıştırılır
    2- Uygulamaların veritabanı dosyaları şu dizin yapısı altında tutulmaktadır, “/data/data//databases”
    3- “sqlite3″ komutu veritabanı adı verilerek çalıştırılır
    4- Bu aşamadan sonra sql komutları çalıştırılarak yarattığımız kayıtları görebiliriz.

    31

    Şimdi ise döviz değerlerini daha önceki değerler ile karşılaştırarak değişimleri kullanıcıya gösterme işlemi yapalım. Hatırlarsanız, ListView içerisindeki parçaların gösterimi için bir Adaptor sınıfı hazırlamış ve bu sınıf içerisinde her parçanın nasıl kullanıcıya gösterileceğini belirtmiştik. Değişimleri gösterme işleminde de yine bu noktalarda işlem yapacağız:
    1- Değişikliği gösteren 3 adet imaj dosyası hazırlayacağız
    2- ListView içerisideki parçaların gösterimi için hazrıladığımız layout içerisine iki tane “ImageView” ekleyeceğiz
    3- Adaptor sınıfı içerisinde “getView” metodunda göstereceğimiz değeri veritabanındaki kayıtlar ile karşılaştırıp alış ve satış değerlerindeki değişimleri eklediğimiz iki ImageView içerisinde göstereceğiz.

    Öncelikle 3 tane imaj dosyası hazırlayıp, bunları “/res/drawables” dizini altına yerleştirelim.

    Sonra ListView içindeki parçaların gösterimi için hazırladığımız layout içerisine “ImageView” tanımlarını ekleyelim:

    colors.xml

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <color  name="list_back_color">#FFFF00</color>
        <color  name="header_text_color">#000000</color>
        <color  name="header_back_color">#FFFFFF</color>
        <color  name="currency_name_text_color">#000000</color>
        <color  name="currency_text_color">#000000</color>
        <color  name="currency_name_back_color">#FF3300</color>
        <color  name="currency_back_color">#FFFF00</color>
    </resources>

    currency_item.xml

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    	android:layout_width="fill_parent"
    	android:layout_height="wrap_content"
    >
    	<TextView 
    		android:id="@+id/currencyName"
    		android:layout_width="wrap_content"
    		android:layout_height="fill_parent"
    		android:width="60px"
    		android:textColor="@color/currency_name_text_color"
    		android:background="@color/currency_name_back_color"
    	/>	
    	<ImageView
    		android:id="@+id/currencyChangeBuy"
    		android:layout_width="wrap_content"
    		android:layout_height="fill_parent"
    	/>
    	<TextView
    		android:id="@+id/currencyBuy"
    		android:layout_width="wrap_content"
    		android:layout_height="fill_parent"
    		android:width="60px"
    		android:textColor="@color/currency_text_color"
    		android:background="@color/currency_back_color"
    	/>
    	<ImageView
    		android:id="@+id/currencyChangeSell"
    		android:layout_width="wrap_content"
    		android:layout_height="fill_parent"
    	/>
    	<TextView
    		android:id="@+id/currencySell"
    		android:layout_width="fill_parent"
    		android:layout_height="fill_parent"
    		android:textColor="@color/currency_text_color"
    		android:background="@color/currency_back_color"
    	/>
    </LinearLayout>

    Son olarak ise geliştirdiğimiz Adaptor sınıfı içerisindeki “getView” metodunda ilgili değişikliği yapacağız:

    public class CurrencyItemAdapter extends ArrayAdapter<CurrencyItem> {
    	private Context context;
    	private String  processDate;
     
    	public CurrencyItemAdapter(Context context, int textViewResourceId, CurrencyItem[] objects, String processDate) {
    		super(context, textViewResourceId, objects);
    		this.context 	 = context;
    		this.processDate   = processDate;
    	}
     
    	@Override
    	public View getView(int position, View convertView, ViewGroup parent) {
    		CurrencyItem currencyItem = getItem(position);
    		View 		   currencyItemView;
     
    		if(convertView == null) {
    			LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    			currencyItemView              = layoutInflater.inflate(R.layout.currency_item, null);
    		} else {
    			currencyItemView = convertView;
    		}
     
    		TextView  nameView          = (TextView) currencyItemView.findViewById(R.id.currencyName);
    		TextView  buyView            = (TextView) currencyItemView.findViewById(R.id.currencyBuy);
    		TextView  sellView             = (TextView) currencyItemView.findViewById(R.id.currencySell);
    		ImageView changeBuyView  = (ImageView) currencyItemView.findViewById(R.id.currencyChangeBuy);
    		ImageView changeSellView  = (ImageView) currencyItemView.findViewById(R.id.currencyChangeSell);
     
    		try {
    			CurrencyItem tempItem = CurrencyExchangeDBAdaptor.getInstance(context).getLatestCurrencyExchange(processDate, currencyItem.getName());			
    			if(tempItem == null) {
    				changeBuyView.setImageResource(R.drawable.same);
    				changeSellView.setImageResource(R.drawable.same);
    			} else {
    				if(Double.parseDouble(tempItem.getBuy()) > Double.parseDouble(currencyItem.getBuy())) {
    					changeBuyView.setImageResource(R.drawable.down);				
    				} else if(Double.parseDouble(tempItem.getBuy()) < Double.parseDouble(currencyItem.getBuy())) {
    					changeBuyView.setImageResource(R.drawable.up);				
    				} else if(Double.parseDouble(tempItem.getBuy()) == Double.parseDouble(currencyItem.getBuy())) {
    					changeBuyView.setImageResource(R.drawable.same);					
    				}	
    				if(Double.parseDouble(tempItem.getSell()) > Double.parseDouble(currencyItem.getSell())) {
    					changeSellView.setImageResource(R.drawable.down);				
    				} else if(Double.parseDouble(tempItem.getSell()) < Double.parseDouble(currencyItem.getSell())) {
    					changeSellView.setImageResource(R.drawable.up);				
    				} else if(Double.parseDouble(tempItem.getSell()) == Double.parseDouble(currencyItem.getSell())) {
    					changeSellView.setImageResource(R.drawable.same);									
    				}
    			}					
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
     
    		nameView.setText(currencyItem.getName());
    		buyView.setText(currencyItem.getBuy());
    		sellView.setText(currencyItem.getSell());
     
    		return currencyItemView;
    	}
    }
    </java>

    Uygulamamızı çalıştırırsak:

    4

    Ve böylece uygulamamızın 2. adımınıda bitirmiş olduk, 3. adımda tekrar buluşmak üzere.

    , , ,

    3 Comments