Xamarin ile Instagram Uygulaması Yazmak
Selamlar;
Öncelikle bunca zamandır Azure üzerinde paylaştığım Url’lerin hepsi değişmiştir. Ilgili yeni Url’leri bloğumdan erişebilirsiniz.
Konu ile ilgili video aşağıdadır:
Bugün Visual Studio 2015 ve Xamarin ile android cihazlar için cep telefonu ile çekilen yada gallery’den seçilen bir resimin belirlenen bir web sayfasında real time olarak görünmesini sağlayacağız. Amaç eventlerde detaylı olarak anlatacağım bu konun şimdiden kullanılan mimarisinden ve belli başlı kritik noktlarından bahsetmektir. Öncelikle doğru bir mimari çizmek hayati bir önem taşımaktadır.
Mobil uygulamamız restfull bir webapi servisi ile konuşup ilgili resimi yollar. Daha sonra alınan resim bir storage’da saklanır. Bu bir Image Cdn veya Azure Blob Storage olabilir. Ilgili resim signalR kullanılarak tüm clientlara real time olarak push edilir. Mvc View tarafında AngularJs kullanılarak ilgili modele yeni gönderilen resim eklenerek, sıralanan list item’da gözükmesi sağlanır. Böylece çekilen resim sayfadaki tüm clientlara gösterilmiş olur.
Aşağıda görüldüğü gibi mobile uygulama için Camera, Internet ve Write_External_Storage izinlerinin verilmesi gerekmektedir.
Ekran tasarımı olarak Camera’yı açan bir button, var olan resimlerden seçim yapmayı sağlayan bir button bir de Web Api servisine ilgili resmi gönderen bir başka button bulunmaktadır. Seçilen veya camera ile çekilen resimi kullanıcaya göstermek için bir de image view bulunmaktadır.
Main.axml: Design kodları 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 27 |
<?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"> <Button android:id="@+id/myButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/openCamera" /> <Button android:text="@string/selectImage" android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/selectButton" /> <Button android:id="@+id/sendButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/sendImage" /> <ImageView android:src="@android:drawable/ic_menu_gallery" android:layout_width="fill_parent" android:layout_height="300.0dp" android:id="@+id/imageView1" android:adjustViewBounds="true" /> </LinearLayout> |
Fotoğraf çekme button’una basılınca IsThereAnAppToTakePictures function’ı ile mobil cihazda fotoğraf çekmeye uygun bir uygulama varmı diye check edilir.
Alttaki komut ile ilgili resim, gerekli izinlerin verilmesi şartı ile çekilir.
intent.PutExtra(MediaStore.ExtraOutput, Android.Net.Uri.FromFile(App._file));
OnActivityResult() methodunda çekilen resim Image View’a sığdırılarak yani resize edilerek atanır. Full size olarak çalışmak çok fazla memory’den yiğip uygulamanın kitlenmesine neden olabilir. Örnek kod aşağıdaki gibidir. LoadAndResizeBitmap() custom yazılan bir extension method’dur.
1 2 3 4 |
int height = Resources.DisplayMetrics.HeightPixels; int width = _imageView.Width; App.bitmap = App._file.Path.LoadAndResizeBitmap(width, height); _imageView.SetImageBitmap(App.bitmap); |
Aynı şekilde gallery’den var olan resim seçildiği zaman alttaki kod işletilerek ilgili galeri ekranı açılır. Daha sonra ilgili resim seçilince ilgili image url’i alınıp gene resize işleminden sonra Image View’a atanarak, user’a gösterilir.
1 2 3 4 5 6 |
var imageIntent = new Intent(); imageIntent.SetType("image/*"); imageIntent.SetAction(Intent.ActionGetContent); StartActivityForResult( Intent.CreateChooser(imageIntent, "Select photo"), 0); |
Var olan galeriden seçilen image’in path’inin bulnması için ayrıca GetPathToImage() adlı custom bir method yazılır. Amacı Android.Net.Uri‘yi string url path olarak alabilmektir.
Send Image buttonuna basıldığında ilgili WebApi restfull servise istenen image Client.UploadFile() methodu ile gönderilir.
Gönderme işlemi tamamlanınca çıkacak sesli bildiri Proje’de Resources/raw şeklinde açılan folder’a konan .wav dosyalarından erişilir. Mesela upload.wav’a, Resource.Raw.upload şeklinde erişilebilir.
Çalma işlemi Android.Media.MediaPlayer.Create() methodu ile gerçekleşmektedir. Ayrıca resim gönderildikten sonra _imageView’e istene bir default resim atanabilmesi için, Resources/drawable altına belirlenen bir Icon.png atılarak Resource.Drawable.Icon şeklinde set edilebilir. Folder’a atılan bir dosyaya sanki enum bir nesne gibi erişmek, mobil dünyanın ne kadar farklı olduğunu bize birdaha kanıtlamaktadır.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
private void SendPicture(object sender, EventArgs eventArgs) { try { System.Net.WebClient Client = new System.Net.WebClient(); Client.Headers.Add("Content-Type", "binary/octet-stream"); byte[] result = Client.UploadFile("http://xxxxxxxx.azurewebsites.net/api/home", "POST",App._file.Path); string Result_msg = System.Text.Encoding.UTF8.GetString(result, 0, result.Length); //Play Sound player = Android.Media.MediaPlayer.Create(this, Resource.Raw.upload); player.Start(); ----------------------- Toast.MakeText(this, Result_msg, ToastLength.Long).Show(); _imageView.SetImageResource(Resource.Drawable.Icon); } catch (Exception ex) { Toast.MakeText(this, ex.Message, ToastLength.Long).Show(); } |
Alınan resimler öncelikle MobileImages folderına kaydedilip daha sonra ilgili azure blob storage’a atılır. Daha sonra eklenen resim datalarını tüm clientlara push edilmesi için ilgili signalR GetImages hub classın’daki AddImage() method’u aşağıdaki gibi eklenen resim parametresi ile triger edilir. Bütün bu işlemler asenkron olarak yapılır.
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 |
[System.Web.Http.HttpPost] public async Task<HttpResponseMessage> SavePicture() { if (!Request.Content.IsMimeMultipartContent()) { throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType); } string fileSaveLocation = HttpContext.Current.Server.MapPath("~/MobileImages"); CustomMultipartFormDataStreamProvider provider = new CustomMultipartFormDataStreamProvider(fileSaveLocation); List<string> files = new List<string>(); try { // Read all contents of multipart message into CustomMultipartFormDataStreamProvider. await Request.Content.ReadAsMultipartAsync(provider); string image = ""; foreach (MultipartFileData file in provider.FileData) { ...Azure blob storage'a kaydedilir. } var hubConnection = new HubConnection("http://bistagrams.azurewebsites.net/"); IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("GetImages"); await hubConnection.Start(new LongPollingTransport()); stockTickerHubProxy.Invoke("AddImages", files.First()); // Send OK Response along with saved file names to the client. return Request.CreateResponse(HttpStatusCode.OK, files); } catch (System.Exception e) { return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e); } } |
Aşağıda ilgili GetImages Hub class’ının kodları görülmektedir. AddImages() methodu ile ilgili resim clientlar’ın addImages() function’ına gönderilerek tetiklenir. Sayfa ilk yüklendiğinde hub classına connect olunduğunda OnConnected() methodu devreye girer ve azure blob storage’dan çekilen resimler, ListModel olarak tüm clientların reloadImages() function’ına gönderilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class MyViewModel { public string ImageUrl { get; set; } } public class GetImages : Hub { public override async Task OnConnected() { ..... await Clients.Caller.reloadImages(ListModel); } public void AddImages(string images) { MyViewModel model = new MyViewModel(); model.ImageUrl = "/MobileImages/" + Path.GetFileName(images); Clients.All.addImages(model); } } |
http://bistagrams.azurewebsites.net/: İlgili sayfa Mvc 6.0 ve AngularJS ile aşağıda görüldüğü gibi tasarlanmıştır.
Resimler aşağıda görüldüğü gibi angularJS ile listelenmiştir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<body background="~/Content/wallpaper.jpg"> <h2><font color="white"> Share Your Photos With Bistagram</font></h2> <div ng-app="" ng-controller="Controller"> <input type="button" ng-show="isShow" ng-click="deleteImages()" style="background-image: url('/Content/del5.jpg');width:130px; height:63px;" ;> <table> <tr ng-repeat="images in datas"> <td ng-repeat="image in images" style="padding:2px"> <input type="checkbox" ng-show="isShow" ng-model="image.isDelete"> <a class="grouped_elements" rel="group1" href="{{image.ImageUrl}}"> <div><img src="~/Content/open.png" width="25" height="25" /></div> </a> <img ng-src={{image.ImageUrl}} alt="BoraKaşmer" width="200" height="200" ng-dblclick="showAll()" style="border:8px solid white;" /> </td> </tr> </table> </div> </body> |
İlgili hub classına aşağıdaki gibi bağlandıktan sonra hubProxy’e ait reloadImages() function’ı ile ilgili resim datası 3’er li guruplar halinde angularJs $scope.datas‘ına eklenir. HubProxy’e ait birbaşka addImages() functionında tüm liste fullDataset lisitesine eklenerek temizlenir. Daha sonra yeni gelen image başta eklenmek şartı ile 3 erli guruplar halinde tekrardan $scope.datas‘ına doldurulur. Böylece yeni gelen image tüm clientlarda en başta görünmüş olur. Yapı angularJs ile kurgulandığından datas değiştiğinden buna bağlı tüm objeler bundan etkilenir. Buna bağlı olarak yukarıda görülen ilgili tabloda da yeni eklenen resim gözükür. Tüm bu işlemlerin tersi silme işlemi için geçerlidir.
Resimler 2 kere clicklendiğinde üstlerinde çıkan checkboxlar seçilip delete button’una basılınca ilgili data’dan seçili resimler çıkarılır. ve IsDeleted propertyleri true olarak atanır. Silinmiş olarak işaretlenen image dataları server side webapi deletePicture() methoduna gönderilir. İlgili storage’dan silinen resimler, diğer tüm clientların da bu durumdan haberdar olması adına signalR Hub class’ının DeleteListPush() methoduna gönderilir. Burada silinene resim listesi clientlar’daki HubProxy’e ait deletedListPress() function’ına gönderilerek $scope.datas‘dan çıkarılması ve ekrandan kaldırılması sağlanır. Delete işlemi ve bazı diğer durumlar için detaylı kodları eventlerime katılarak edinebilirsiniz. Gerekli bildirimleri twitterdan duyurucağım.
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 |
function Controller($scope, $http) { var hubProxy = $.connection.getImages; $scope.isShow = false; hubProxy.client.reloadImages = function (data) { console.log("client.reloadImages( " + JSON.stringify(data) + " )"); $scope.datas = []; dataSet = []; for (var i = 0; i < data.length; i++) { data[i].isDelete = false;//New if (i % 3 == false) { $scope.datas.push(dataSet); dataSet = []; } dataSet.push(data[i]); } if (dataSet.length > 0) { $scope.datas.push(dataSet); dataSet = []; } //$scope.datas = data; $scope.$apply(); } hubProxy.client.addImages = function (image) { console.log("client.addImages( " + JSON.stringify(image) + " )"); fullDataSet = []; fullDataSet.push(image); angular.forEach($scope.datas, function (images) { angular.forEach(images, function (image) { fullDataSet.push(image) }); }); //-------------------- $scope.datas = []; dataSet = []; for (var i = 0; i < fullDataSet.length; i++) { fullDataSet[i].isDelete = false; if (i % 3 == false) { $scope.datas.push(dataSet); dataSet = []; } dataSet.push(fullDataSet[i]); } if (dataSet.length > 0) { $scope.datas.push(dataSet); dataSet = []; } //$scope.datas = data; $scope.$apply(); } |
Bu projeden de anlaşılacağı gibi doğru mimariyi seçmek hayati önem taşımaktadır. Bu uygulamada da görüldüğü gibi mobil ve web application teknolojileri bir araya getirilerek gayet kullanışlı bir ürün ortaya çıkarılabilmektedir.
Yeni bir makalede görüşmek üzere hepinize hoşçakalın.
Source: https://stackoverflow.com/questions/39784095/xamarin-android-app-issue
Hocam Selamlar;
Harika bir makale olmuş. Daha iyi bir örnek bu konuda varsa göstersinler. Şimdiye kadar kimsenin yorum yapmaması çok enteresan:) Resimlerde bir de düzenleme olsa bitti gitti. Çok beyendim. Elinize sağlık.
İyi çalışmalar.
Selamlar Bora Hocam;
Eventinizi iple çekiyorum. Elinize sağlık. Ben de veli gibi çok beyendim. Muhteşem örnek.
İngilizce yapmaya devam edin bence. Yurtdışında da dikkat çekeceği kesin. Ama burdaki gibi türkçe yazıyı ihmal etmeyin hocam.
İyi yıllar….
Paylaştıklarınız özgün, anlaşılır ve güncel. Süper ötesi diyebilirim :) Tebrikler.
Hocam Selamlar;
Hep yapmak istediğim birşeyi siz yapmışınız. Tabi ben sadece hayal ediyordum ama siz yapmışınız. Emeğiniz için teşekkür ederim. Cidden yine harika bir makale ve video olmuş. Çok teşekkürler. Ve iyi çalışmalar.
İyi yıllar.
Selamlar;
Öncelikle çok güzel bir paylaşım. Zaten daha güzel bir örnek şu an itibari ile de yok:) AngularJS ile ilgili tüm makalelerinizi okudum, hatta tüm videolarınızı izledim. Komple bir kitap okumuş gibi oldum.. Şu an rahatlıkla I know angularJS diyebilirm:) Demin ilk uygulamamı da yazdım.
Bu kadar kaliteli paylaşımlar için yine çok teşekkür ederim.
İyi çalışmalar.
Oldukça sade, anlaşılır şekilde anlatmışsınız. Sitenizi takipteyim, iyi paylaşımlar yapıyorsunuz. Elinize sağlık.
çok güzel bir anlatım devamı sanırım olmayacak xamarin in ama olmasınıda çok isterdim
Selamlar Erdem;
Öncelikle teşekkürler.
Yok olmayacak diye birşey yok! Sırası gelince o konuda da yazıcam..
Merhaba,
Sizden öğrenilecek çok şey var elinize sağlık bilmek ve anlatabilmek ikisi de sizde var.
Teşekkürler Ahmet..