Posts Tagged Adaptor

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	currenyItemVector;
    	private int					state;
    	private StringBuilder 			processDate;
	private String 				currencyName;
    	private String				currencyBuy;
    	private String				currencySell;

    	public CurrencyExchangeHandler(StringBuilder processDate,Vector 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):


#FFFF00
#000000
#000000
#000000
#FF3300
#FF9900
#FFFF00

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


	
	
	

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:




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 {

	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 currenyItemVector        = new Vector();
    	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

    Currency Exchange Values on

colors.xml

#000000
#FFFFFF

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






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  currenyItemVector       = new Vector();
    	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



#FFFF00
#000000
#FFFFFF
#000000
#000000
#FF3300
#FFFF00

currency_item.xml


	
	
	
	
	

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 {
	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;
	}
}

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.

, , ,

No Comments