Xamarin ile Visual Studio 2015’de Android Programlama Bölüm2
Selamlar;
Bugünkü uygulamamızda bir Webservis’den aldığımız haberleri android telefon üzerinde göstericeğiz. Haberleri servisden çekmeden önce mobile cihazın internet durumuna bakıcağız. Internet var ise Wifi durumuna göre servise bağlanacağız. Telefonu salladığımız zaman bir sonraki haber gurubunu çekiceğiz. İlgili haberlere tıklayınca detayını okuyacağız. Böylece telefonun farklı farklı birçok özelliğini kullanmış olucağız.
Öncelikle haberleri göstermek için Main.axml dosyasına aşağıda görüldüğü gibi button eklemek gerekir. Text’i “@string/GameListButton” olarak tanımlanmıştır. Strings.xml deki karşılığı Oyun Haberleridir.
Strings.xml: Oyun Haberleri Buttonu text’i aşağıdaki satır eklenerek tanımlanır.
1 2 3 4 |
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="GameListButton">Oyun Haberleri</string> </resources> |
Main.axml: Oyun Haberleri buttonun propertyleri için alttaki satırlar eklenir.
1 2 3 4 5 |
<Button android:text="@string/GameListButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/CallGameListButton" /> |
MainActivity.cs: Oyun Haberler’i button’una tıklanınca ilgili sayfaya gitmek için gerekli kodlar aşağıdaki gibi tanımlanmıştır.
1 2 3 4 5 |
callGameListButton.Click += (sender, e) => { var intent = new Intent(this, typeof(GameListActivity)); StartActivity(intent); }; |
Şimdi gelelim Oyun Haberleri yani GameListActivity.cs sayfasına. Öncelikle mobile cihazın internet durumunu aşağıdaki gibi kontrol edelim. 3 durum söz konusudur. GetSystemService() methodu ile öncelikle ConnectivityManager class’ına erişilir. İlgili classın ActiveNetworkInfo propertysi ile internet bağlantısı olup olmadığına bakılır. Internet var ise GetNetworkInfo(ConnectivityType.Wifi).GetState() methodu ile bağlantı tipinin WiFi olup olmadığına bakılır. Wifi var ise State “2” döndürülür. Internet var ama Wifi yok ise State “1” döndürülür. Hiç internet yok ise State “0” döndürülür. Ve kullanıcı ilgili duruma göre bilgilendirilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public int CheckOnline() { var connectivityManager = (ConnectivityManager)GetSystemService(ConnectivityService); var activeConnection = connectivityManager.ActiveNetworkInfo; if ((activeConnection != null) && activeConnection.IsConnected) { var mobileState = connectivityManager.GetNetworkInfo(ConnectivityType.Wifi).GetState(); if (mobileState == NetworkInfo.State.Connected) { return 2; } return 1; } return 0; } |
Yukarıdaki telefonun özelliklerine erişebilmek için aşağıdaki izinleri Properties çift tıklanarak Android Mainfest sekmesi altında ilgili permissions’lar işaretlenerek verilir.
Uygulamanın yüklenme sırasında ilgili izinler için kullanıcı aşağıda görüldüğü gibi uyarılır.
İnternet’in bağlantı durumu WiFi ise yani State “2” ise ilgi Web servisine bağlanılarak oyun haberleri çekilir. Oyun haberlerini Steam’den aşağıdaki url’den çekicez. Ilgili servis’e bağlanabilmek için telefona yukarıda görülen Tam internet erişim yetkisi verilmesi gerekmektedir. İlgili servisten 20 adet haber Maximum 300 karakter olucak şekilde json formatında çekilmektedir.
1 |
http://api.steampowered.com/ISteamNews/GetNewsForApp/v0002/?appid=570&count=20&maxlength=300&format=json |
Örnek Result aşağıdaki gibidir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
{ "appnews": { "appid": 570, "newsitems": [ { "gid": "522745760225475952", "title": "Dota 2 Update - December 8th, 2014", "url": "http://store.steampowered.com/news/15197/", "is_external_url": false, "author": "Valve", "contents": "- Fixed various pathing issues when moving around trees - Fixed Match Ready dialog ignoring clicks sometimes - Fixed the fifth game in the series popup drawing off the bottom of the screen in the Watch panel", "feedlabel": "Product Update", "date": 1418082720, "feedname": "steam_updates" }, { "gid": "522745760217071688", "title": "Upcoming Dota 2 engine changes means no Frostivus for 2014", "url": "http://store.steampowered.com/news/externalpost/pcgamer/522745760217071688", "is_external_url": true, "author": "", "contents": "<a href=\"/upcoming-dota-2-engine-changes-means-no-frostivus-for-2014/\"></a>In today's bit of good news/bad news, Valve's Dota team says a \"major improvement\" to the <a href=\"http://www.pcgamer.com/dota-2/\">Dota 2</a> engine is in the works and will be released sometime during the first half of 2015, enabling the ability to quickly create entirely new game modes. Unfortunately, that means Frostivus won't be happening ...", "feedlabel": "PC Gamer", "date": 1418069205, "feedname": "pcgamer" }, |
MyData: İlgili servisten çekilen datanın atılacağı View Model MyData’dır. Burada Key haberin başlığı Value‘de haberin detayıdır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
MyData[] results = null; public class MyData { public string Key { get; set; } public string Value { get; set; } public string getText() { return Key; } public String getValue() { return Value; } public override string ToString() { return Key; } } |
- Ilgli servise bağlanmak için aşağıdaki HttpWebRequest class’ı kullanılmıştır.
var httpReq = (HttpWebRequest)HttpWebRequest.Create(new System.Uri(searchUrl));
- İlk sayfa açıldığında ilgili haberler response.GetResponseStream() methodu ile bir stream objesine çekilerek JsonObject nesnesine convert edilir. Ilgili Json nesnesinden aşağıdaki linq sorgusu ile ilk 5 kayıt alınıp MyData View Model’i doldurulur.
results = (from result in (JsonArray)j[“appnews”][“newsitems”]
let jResult = result as JsonObject
select new MyData
{
Key = jResult[“title”].ToString(),
Value = jResult[“contents”].ToString()
}).ToArray();
var results2 = results.Take(5).ToArray();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
try { int resultWifi = CheckOnline(); if (resultWifi == 2) //WiFi var uygulama çalışır. { string searchUrl = String.Format("http://api.steampowered.com/ISteamNews/GetNewsForApp/v0002/?appid=570&count=20&maxlength=300&format=json"); var httpReq = (HttpWebRequest)HttpWebRequest.Create(new System.Uri(searchUrl)); httpReq.BeginGetResponse((ar) => { var request = (HttpWebRequest)ar.AsyncState; using (var response = (HttpWebResponse)request.EndGetResponse(ar)) { var s = response.GetResponseStream(); var j = (JsonObject)JsonObject.Load(s); results = (from result in (JsonArray)j["appnews"]["newsitems"] let jResult = result as JsonObject select new MyData { Key = jResult["title"].ToString(), Value = jResult["contents"].ToString() }).ToArray(); var results2 = results.Take(5).ToArray(); RunOnUiThread(() => { ListAdapter = new ArrayAdapter<MyData>(this, Android.Resource.Layout.SimpleListItem1, Android.Resource.Id.Text1, results2); }); } }, httpReq); } else if (resultWifi == 1) //Kullanıcı Bilgilendirilmesi Internet var WiFi yok. { Android.Widget.Toast.MakeText(this, "Internet var. Ama sadece wifi ile çalışır", Android.Widget.ToastLength.Short).Show(); } else //Kullanıcı Bilgilendirilmesi. Internet Yok. { Android.Widget.Toast.MakeText(this, "Internet Yok !!!!", Android.Widget.ToastLength.Short).Show(); } } catch (Exception EX) { Android.Widget.Toast.MakeText(this, EX.Message, Android.Widget.ToastLength.Short).Show(); int i = 0; } |
- Alınan 5 haber Thread ile ListAdapter’a atanıp user’a gösterilir. İşlemin arkada çalışması uygulamanın telefonu kitlememesi açısından çok önemlidir.
RunOnUiThread(() =>
{
ListAdapter = new ArrayAdapter<MyData>(this,
Android.Resource.Layout.SimpleListItem1, Android.Resource.Id.Text1, results2);
});
- Geldik mobile cihazımızın Accelerator özelliğini kullanarak, telefonun sallanma durumunda bir sonraki 5 haberi göstermesi senaryosuna:
Telefonun son x.y.z kordinatllarının tutulacağı last_x, last_y ve last_z değişkenleri, 2 salınım arasında enaz 150 milisecond zaman geçmesini sağlıyan ShakeDetectionTimeLapse ve lastUpdate değişkenleri, bir sonraki haberler çekilirken aynı anda bir istek daha gelmemesini sağlıyan hasUpdated değişkeni, sallanma hızını belirleyen ShakeThreshold değişkeni ve son olarak enson kaçıncı sayfadaki haberlerin çekildiğini tutan page değişkeni aşağıdaki gibi tanımlanmıştır.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//Shake parameters bool hasUpdated = false; DateTime lastUpdate; float last_x = 0.0f; float last_y = 0.0f; float last_z = 0.0f; bool doIT=true;//For Services int page=0;//For Services const int ShakeDetectionTimeLapse = 150; const double ShakeThreshold = 500; //-------------------------------------------- |
İlgili video aşağıdadır:
Mobile cihazın salınım durumunda OnSensorChanged() methodu tetiklenir. Nedeni classımız aşağıda görüldüğü gibi ISensorEventListener interface’inden türetilmiştir.
public class GameListActivity : ListActivity, Android.Hardware.ISensorEventListener
Yukarıdaki formüle göre cihazın( (son x,y,z kordinatları ile – ilk x.y.z kordinatlarının) farkı alınıp /, (son hareket zamanı ile – ilk hareket zamanın) milisecond cinsine göre zaman farkına bölümünden çıkan hız) >500 ise salınım gerçekleştiği kabul edilir. 500 değeri görecelidir. İstendiği gibi ayarlanabilir. Eğer salınım işlemi var ise pageList Listesinde 1,2,3 page numberlarından olmayan ilk sayfa alınarak ilgili data yandaki gibi çekilir. Amaç önceden gelmemiş bir haberin gelmesini sağlamaktır. results.Skip(5 * page).Take(5).ToArray(); Daha sonra çekilen 5 oyun haberi datası ListAdapter’a RunOnUiThread(() => thread objesi ile gene arkada çalışan bir yapı ile atanarak, kullanıcıya gösterilir. Bu işlem sırasında Android.Widget.Toast.MakeText() methodu ile yukarıda görüldüğü gibi kullanıcı ekranın altında beliren bir text ile kaçıncı sayfadan data çekildiği bilgisi verilir. Dikkat edilecek olursa, gösterilecek toplam 20 haber servisten daha enbaştan uygulama ilk yüklenilirken çekilmiştir. Böylece telefon sallandığında bir sonraki çekilecek 5 haber için servise bağlanılmamış ve var olan kaynaktan çekilmiştir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
List<int> pageList = new List<int>(); public void OnSensorChanged(Android.Hardware.SensorEvent e) { if (results != null) { if (e.Sensor.Type == Android.Hardware.SensorType.Accelerometer) { float x = e.Values[0]; float y = e.Values[1]; float z = e.Values[2]; DateTime curTime = System.DateTime.Now; if (hasUpdated == false) { hasUpdated = true; lastUpdate = curTime; last_x = x; last_y = y; last_z = z; } else { if ((curTime - lastUpdate).TotalMilliseconds > ShakeDetectionTimeLapse) { float diffTime = (float)(curTime - lastUpdate).TotalMilliseconds; lastUpdate = curTime; float total = x + y + z - last_x - last_y - last_z; float speed = Math.Abs(total) / diffTime * 10000; if (speed > ShakeThreshold) { if (doIT) { doIT = false; if (!pageList.Contains(1)) { page = 1; pageList.Add(1); } else if (!pageList.Contains(2)) { page = 2; pageList.Add(2); } else if (!pageList.Contains(3)) { page = 3; pageList.Add(3); } else { pageList.Clear(); page = 0; } var results2 = results.Skip(5 * page).Take(5).ToArray(); RunOnUiThread(() => { ListAdapter = new ArrayAdapter<MyData>(this, Android.Resource.Layout.SimpleListItem1, Android.Resource.Id.Text1, results2); }); doIT = true; System.Threading.Thread.Sleep(2000); Android.Widget.Toast.MakeText(this, "Yeni Haberler çekiliyor. Page:" +page.ToString(), Android.Widget.ToastLength.Long).Show(); } } last_x = x; last_y = y; last_z = z; } } } } #endregion } } |
İlgili haberin tıklanmasından sonra haber detayının gösterilmesi aşağıdaki gibi OnListItemClick() methodu ile ViewModel olarak kullandığımız MyData class’ının getValue() methodu ile ilgili Key’in Value değerini göstermesi ile olmaktadır.
1 2 3 4 5 |
protected override void OnListItemClick(ListView l, View v, int position, long id) { var t = results[position]; Android.Widget.Toast.MakeText(this, t.getValue(), Android.Widget.ToastLength.Short).Show(); } |
GameListActivity.cs (Tüm Kod) :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Android.App; using Android.Content; using Android.OS; using Android.Runtime; using Android.Views; using Android.Widget; using System.Net; using System.Json; using Android.Net; namespace TestAndroid { [Activity(Label = "@string/GameListButton")] public class GameListActivity : ListActivity, Android.Hardware.ISensorEventListener { MyData[] results = null; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); try { int resultWifi = CheckOnline(); if (resultWifi == 2) { string searchUrl = String.Format("http://api.steampowered.com/ISteamNews/GetNewsForApp/v0002/?appid=570&count=20&maxlength=300&format=json"); var httpReq = (HttpWebRequest)HttpWebRequest.Create(new System.Uri(searchUrl)); httpReq.BeginGetResponse((ar) => { var request = (HttpWebRequest)ar.AsyncState; using (var response = (HttpWebResponse)request.EndGetResponse(ar)) { var s = response.GetResponseStream(); var j = (JsonObject)JsonObject.Load(s); results = (from result in (JsonArray)j["appnews"]["newsitems"] let jResult = result as JsonObject select new MyData { Key = jResult["title"].ToString(), Value = jResult["contents"].ToString() }).ToArray(); var results2 = results.Take(5).ToArray(); RunOnUiThread(() => { ListAdapter = new ArrayAdapter<MyData>(this, Android.Resource.Layout.SimpleListItem1, Android.Resource.Id.Text1, results2); }); } }, httpReq); } else if (resultWifi == 1) { Android.Widget.Toast.MakeText(this, "Internet var. Ama sadece wifi ile çalışır", Android.Widget.ToastLength.Short).Show(); } else { Android.Widget.Toast.MakeText(this, "Internet Yok !!!!", Android.Widget.ToastLength.Short).Show(); } } catch (Exception EX) { Android.Widget.Toast.MakeText(this, EX.Message, Android.Widget.ToastLength.Short).Show(); int i = 0; } // Shake Manager Register this as a listener with the underlying service. var sensorManager = GetSystemService(SensorService) as Android.Hardware.SensorManager; var sensor = sensorManager.GetDefaultSensor(Android.Hardware.SensorType.Accelerometer); sensorManager.RegisterListener(this, sensor, Android.Hardware.SensorDelay.Normal); } protected override void OnListItemClick(ListView l, View v, int position, long id) { var t = results[position]; Android.Widget.Toast.MakeText(this, t.getValue(), Android.Widget.ToastLength.Short).Show(); } public int CheckOnline() { var connectivityManager = (ConnectivityManager)GetSystemService(ConnectivityService); var activeConnection = connectivityManager.ActiveNetworkInfo; if ((activeConnection != null) && activeConnection.IsConnected) { var mobileState = connectivityManager.GetNetworkInfo(ConnectivityType.Wifi).GetState(); if (mobileState == NetworkInfo.State.Connected) { return 2; } return 1; } return 0; } //Shake parameters bool hasUpdated = false; DateTime lastUpdate; float last_x = 0.0f; float last_y = 0.0f; float last_z = 0.0f; bool doIT=true;//For Services int page=0;//For Services const int ShakeDetectionTimeLapse = 150; const double ShakeThreshold = 500; //-------------------------------------------- #region Android.Hardware.ISensorEventListener implementation public void OnAccuracyChanged(Android.Hardware.Sensor sensor, Android.Hardware.SensorStatus accuracy) { } List<int> pageList = new List<int>(); public void OnSensorChanged(Android.Hardware.SensorEvent e) { if (results != null) { if (e.Sensor.Type == Android.Hardware.SensorType.Accelerometer) { float x = e.Values[0]; float y = e.Values[1]; float z = e.Values[2]; DateTime curTime = System.DateTime.Now; if (hasUpdated == false) { hasUpdated = true; lastUpdate = curTime; last_x = x; last_y = y; last_z = z; } else { if ((curTime - lastUpdate).TotalMilliseconds > ShakeDetectionTimeLapse) { float diffTime = (float)(curTime - lastUpdate).TotalMilliseconds; lastUpdate = curTime; float total = x + y + z - last_x - last_y - last_z; float speed = Math.Abs(total) / diffTime * 10000; if (speed > ShakeThreshold) { if (doIT) { doIT = false; if (!pageList.Contains(1)) { page = 1; pageList.Add(1); } else if (!pageList.Contains(2)) { page = 2; pageList.Add(2); } else if (!pageList.Contains(3)) { page = 3; pageList.Add(3); } else { pageList.Clear(); page = 0; } var results2 = results.Skip(5 * page).Take(5).ToArray(); RunOnUiThread(() => { ListAdapter = new ArrayAdapter<MyData>(this, Android.Resource.Layout.SimpleListItem1, Android.Resource.Id.Text1, results2); }); doIT = true; System.Threading.Thread.Sleep(2000); Android.Widget.Toast.MakeText(this, "Yeni Haberler çekiliyor. Page:" +page.ToString(), Android.Widget.ToastLength.Long).Show(); } } last_x = x; last_y = y; last_z = z; } } } } #endregion } } public class MyData { public string Key { get; set; } public string Value { get; set; } public string getText() { return Key; } public String getValue() { return Value; } public override string ToString() { return Key; } } } |
Bu örnekden de anlaşılacağı gibi mobile ile yapılabilecek işlerin sınırı yoktur. Sınır sizin hayel gücünüzdür. Unutulmaması gereken tek şey mobile için yazlacak uygulamaların light olması gerektiği ve optimizasyonun önemidir. Sonucta bir pc değildir. Telefonun erişilmesi gereken tüm özellikleri için izin alınması gerektiğide akıldan çıkarılmaması gereken bir husustur.
Source Code : http://www.borakasmer.com/projects/PhoneTest.rar
Yeni bir makalede görüşmek üzere hoşçakalın.
Selamlar Hocam;
Gene muhteşem bir makale olmuş. Kitap gibi baştan sona nefessiz okudum. Akşam evde aynen deniyeceğim.
Böyle güzel yazılar ve emeğiniz için çok teşekkür ederim.
Teşekkürler Murat. Sizin gibi kıymetini bilen developerlar oldukça daha çok yazım olur!!
Hocam elinize saglik.
Harika bir makale olmus. Cok yonlulugunuze hayranim. Gecen microsoft’un seminerinde telefonun arka rengini random degistirdiler. Keske sizde katilsa idiniz hocam! Umarim mobile uzerine ornekleriniz devam eder.
Iyi calismalar.
Tesekkürler Ömer. Ben bahsettiğin semineri online izleyebildim. Ziyaretçi olarak katılacaktım. Ama akşam bir işim çıkmıştı. Yoksa yakın arkadaşım Engin Polatı kaçırmazdım:)
Hocam ellerinize sağlık mükemmel bir yazı olmuş. Özellikle mobil üzerine yazılarınızın devam etmenizi istiyorum.
Teşekkürler Hayri. Aslında ben web application konuları uzmanıyım. Ama size güzel ve kapsamlı bir örnek hazırlıyorum.
Ya seminerde ya da makale olarak sizlerle paylaşacam.
Selamlar Hocam;
Nerde ise bu konuda türkçe hiç kaynak yok iken böyle bir makale çölde su gibi oldu hocam. Elinize sağlık. Benim merak ettiğim bir konu var. Öncelikle kodları nezaman koyucaksınız? Bir de sallama eylemini daha hafif bir şekilde yapınca anlamasını sağlamak için ne yapabiliriz?
Yakında semineriniz olursa katılmaktan mutluluk duyarım hocam.
Hoşçakalın.
Teşekkürler Fırat.Öncelikle ilgin için teşekkürler. Kodları koydum. Sallamanın şiddetini azaltmak istiyorsan öncelikle ShakeThreshold = 500; olan değeri daha aşağıya mesela 300 gibi bir değere çeker isen daha yavaş bir hızda da event tetiklenmiş olur. Yine const int ShakeDetectionTimeLapse = 150; değerini azaltırsan yine daha hafif sallama eylemi ile yeni haberleri çekebilirsin.
Merhabalar
Yine mükemmel bir yazı olmuş. Zevk ile soluksuz okuyorum ve deneme yanılma yöntemi ile kendimi geliştiriyorum.
Bu uygulamayı yapmak için kulladığınız kaynaklar nedir?
Merak ettiğim bir konu var VS de Xamarin kullanarak IOS uygulaması yaptık diyelim. Bu Xamarin bu uygulamayı hem Android için hemde Windows Phone için derleyip navite hale getirebiliyor mu?
Emeğinize sağlık Teşekkürler.
Teşekkürler Bertan;
Kullandığım kaynaklar Visual Studio Ultimate 2015 Preview, Xamarin Free( Bu nedenle Native uygulama geliştiriyorum.). İşletim sistemi Windows 10. Xamarin Bussines Pack kullanırsan Xamarin Form’da yazabilirsin. O zaman da birkez geliştiriğin kodları hem Android hem Windows Mobile hem de IOS için kullanabilirsin. Hepsi için ayrı ayrı kod geliştirmene gerek kalmaz. Mesela bir alert çıkarıcak isen Android’de ortada, IOS’da yukardan Windows Mobile’da ise alttan çıkacak şekilde Xamarin tarafından derlenir. Yakında Xamarin Bussines Pack kullanacam. Deneyimlerimi paylaşırım.
İyi çalışmalar Bertan. Yazamaya devam:)