Ionic, SignalR ve TypeScript ile Mobile Uygulama Geliştirme Bölüm 2
Selamlar,
Bugünkü makalede, bir önceki makalede kalınan yerden devam edilecektir. Bu yazıya, uygulama ilk açıldığında root olarak belirlenen, kullanıcıyı ilk karşılayan “my-teams” sayfası incelenerek başlanacaktır. İlk sayfa nasıl yaratılır, hangi toolar kullanılır, toggle menu nedir gene bu sayfa üzerinde detaylıca incelenecek konu başlıklarındandır. Daha sonra Ionic’de yaşam döngüsü (LifeCycle), takım detay sayfasına navigasyonun nasıl yapılacağı ve navigasyonda parametre gönderme ile makaleye devam edilecektir. Sayfa yükleniyor ibaresinin oluşturulması, takım detay sayfasının yazılması ve Ionic’de servis mimarisi ile makalenin sonuna gelinecektir. Son olarak projenin tamamında kullanılacak elite-api ve user-settings servislerinin yazılması ile bu hafataki makale tamamlanacaktır.
my-teams(Ana Sayfa) :
my-teams.html:
Ionic’de yeni bir sayfa yaratmanın bir çok farklı yolu vardır. Mesela yeoman generator kullanılabilir. Bu makalede Ionic’in kendi generator’ı kullanılacaktır.
Yeoman için : Alttaki dosyaların yüklenmesi gerekmektedir.
1 2 |
npm install -g yo npm install -g generator-itog |
Yeoman Ionic Page Generator:
1 |
yo itog page |
ionic CLI 3 Generator:
“ionic generate page my-teams” komutu ile Pages Folder’ı altına==> Aşağıda görüldüğü gibi 4 dosya oluşturulur.
Yukarıda görüldüğü gibi uygulama ilk açıldığında karşımıza gelen ekran, “my-teams” sayfasıdır.
- “ <ion-navbar color=”primary”>” yukarıdaki resimde görüldüğü gibi EliteSchedule yazan kısmın arka renginin mavi gözükmesini sağlar.
- “<button menuToggle>” Bu button, menü tıklandığında “app.html”‘in animatif bir şekilde gösterilmesini sağlar. Üstünde 3 çizginin bulundğu “menu” sembolü (“<ion-icon name=”menu”>“) ionic elementi ile sayfaya konur.
- ” <ion-toolbar color=”secondary”>” yeşil ile gösterilen “My Teams” başlık kısmıdır.
- Önemli: “<ion-card *ngIf=”favorites”>” Favour takımlar aynı app.html’de olduğu gibi burada da listelenmektedir. Ama tabi “favorites” [ ] dizisi dolu ise. Eğer favorites dizisi “null” ise, “*ngIf” sayesinde koşul içindeki hiçbir nesne ekrana basılmayacaktır.
- ” <button ion-item *ngFor=”let item of favorites” (click)=”favoriteTapped($event,item)”>” ilgili favourites listesi gezilerek ekrana basılır. (click) eventi ile, tıklanma durumunda seçili takım parametre olarak “favoriteTapped()” methoduna gönderilir.
- “<ion-icon name=”star”>” Favori takım isminin yanına ikon olarak yıldız konur. Diğer ikon listesi buradan incelenebilir.
- “<button ion-button round (click)=”goToTournament()”>” ‘Find Your Tournament’ yazısına tıklandığında turnuvalar sayfasına “goToTournament()” methodu ile gidilmesi sağlanır.
- “<ion-card *ngIf=”!favorites”>” Eğer favorites dizisi boş ise, ana sayfada gözükecek kısım burasıdır. Hiçbir takımın takip edilmediğine dair bir bildirim ve turnuvalar sayfasına giden bir “Find Your Tournament” buttonu bulunmaktadır.
- “<ion-icon name=”search”>” Turnuvalar button’unun yanına bir büyüteç ikonu konmaktadır.
my-teams.html:
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 |
<!-- Generated template for the MyTeamsPage page. See http://ionicframework.com/docs/components/#navigation for more info on Ionic pages and navigation. --> <ion-header> <ion-navbar color="primary"> <button menuToggle> <ion-icon name="menu"></ion-icon> </button> <ion-title>Elite Schedule</ion-title> </ion-navbar> <ion-toolbar color="secondary"> <ion-title>My Teams</ion-title> </ion-toolbar> </ion-header> <ion-content> <ion-card *ngIf="favorites"> <ion-list> <ion-list-header class="my-teams-header">Followed Teams</ion-list-header> <button ion-item *ngFor="let item of favorites" (click)="favoriteTapped($event,item)"> <ion-icon name="star"></ion-icon> {{item.team.name}} <p>{{item.tournamentName}}</p> </button> </ion-list> <ion-card-content> <p>To follow more teams, select tournament, then you can follow teams from their team page. </p> <button ion-button round (click)="goToTournament()"> <ion-icon name="search"></ion-icon> Find Your Tournament </button> </ion-card-content> </ion-card> <!-- <button ion-button secondary menuToggle>Toggle Menu</button> --> <ion-card *ngIf="!favorites"> <ion-card-header>No Followed Teams</ion-card-header> <ion-card-content> <p>You are not currently following any teams.</p> <p>First select Tournament, then you can follow teams from their team page.</p> <button ion-button round (click)="goToTournament()"> <ion-icon name="search"></ion-icon> Find Your Tournament </button> </ion-card-content> </ion-card> </ion-content> |
my-teams.ts: Kullanıcının karşılandığı ana sayfa burasıdır.
- “NavController” linkler arası yönlendirme amaçlı, “NavParams” sayfalar arasında parametre geçişleri ve “LoadingController” sayfa geçişleri arasında yükleniyor yazısının gözükmesi için “ionic-angular” kütüphanesinden import edilen librareylerdir.
- Yönlenecek olan “Tournaments ve TeamHomePage” sayfaya eklenir. Sayfada kullanılacak servisler “EliteApi ve UserSettings”, yine import ile sayfaya eklenir.
- Başta “favorites” olarak static dummy datalar kullanılsa da, sonrasında bunlar yorumlanıp ilgili data “this.favorites = this.userSettings.getAllFavorites();” şeklinde servisden dönen değerler ile atanmıştır.
IONIC PAGE LIFECYCLE:
Aşağıda Ionic bir sayfanın yüklenmesi sırasında, çağrılan methodlar sırası ile görülmektedir. İşte tam da “ionViewDidEnter()”==> methodunda yani bir çeşit “window.load()”‘da , ilgili “favorites” dataları Array’e [ ] eklenmektedir.
- LoadingController : “ionic-angular” kütüphanesi altında sayfaya eklenir.
- Constructer’da dependency injection ile ==>”NavController, NavParams, LoadingController, EliteApi, UserSettings” kütüphaneleri sayfaya import edilir.
- “goToTournament()“: Methodu ile turnuvalar sayfasına “this.navCtrl.push(TournamentsPage)” komutu ile gidilir.
- “favoriteTapped()” methodunda favori takımlardan biri tıklandığında “TeamHomePage” yani Takım Detay sayfasına gidilir. Sayfa yüklenme sırasında ==>”this.loadingController.create({ content: ‘Patlama Bekle :)…’, dismissOnPageChange: true })” şeklinde loader animasyonu yaratılır. Ve gösterilmeside “loader.present();” şeklinde sağlanır.
- “this.eliteApi.getTournamentData(favorite.tournamentId)” service ile seçilen turnuvaya ait takım bulunup ==> “.subscribe(t => this.navCtrl.push(TeamHomePage, favorite.team));” TeamHome Page sayfasına parametre olarak “favorite.team” gönderilir. Böylece ilgili sayfada gösterilecek data önceden çekilip, sayfaya parameter olarak gönderilmiş olunur. Not: Burada dikkat edilmesi gereken durum “getTournamentData()” methodundan dönen “t=>” değişeni ile hiçbir işlemin yapılmadığıdır. Aslında burada birazdan göreceğimiz “this.eliteApi.getTournamentData()” servisinde, seçilen takıma ait turnuva maçları biri dizi içerisine önceden toplanmaktadır.
my-teams.ts:
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 |
import { Component } from '@angular/core'; import { IonicPage, NavController, NavParams, LoadingController } from 'ionic-angular'; import { TournamentsPage } from "../tournaments/tournaments"; import { EliteApi } from "../../shared/elite-api.services"; import { TeamHomePage } from "../pages"; import { UserSettings } from "../../shared/user-settings.service"; /** * Generated class for the MyTeamsPage page. * * See http://ionicframework.com/docs/components/#navigation for more info * on Ionic pages and navigation. */ @IonicPage() @Component({ selector: 'page-my-teams', templateUrl: 'my-teams.html' }) export class MyTeamsPage { favorites:any; /* { team: { id: 6182, name: 'HC Elite 7th', coach: 'Michelotti' }, tournamentId: '89e13aa2-ba6d-4f55-9cc2-61eba6172c63', tournamentName: 'March Madness Tournament' }, { team: { id: 805, name: 'HC Elite', coach: 'Michelotti' }, tournamentId: '98c6857e-b0d1-4295-b89e-2d95a45437f2', tournamentName: 'Holiday Hoops Challenge' } ]; */ constructor(public navCtrl: NavController, public navParams: NavParams, private loadingController: LoadingController, private eliteApi: EliteApi, private userSettings: UserSettings ) { } goToTournament() { this.navCtrl.push(TournamentsPage); } ionViewDidLoad() { console.log('ionViewDidLoad MyTeamsPage'); } ionViewDidEnter() { console.log('ionViewDidEnter MyTeamsPage'); this.favorites = this.userSettings.getAllFavorites(); //console.log(this.favorites); } favoriteTapped($event, favorite) { let loader = this.loadingController.create({ content: 'Patlama Bekle :)...', dismissOnPageChange: true }); loader.present(); this.eliteApi.getTournamentData(favorite.tournamentId) .subscribe(t => this.navCtrl.push(TeamHomePage, favorite.team)); } } |
elite-api.service.ts: Geldik Menu(app.component.ts) ve Ana sayfa’da(my-teams.ts) kullanılan servise:
- baseUrl olarak, kullanılan “firebaseio.com” sayfası belirlenmiştir.
- getTournaments() methodu ile turnuva listesi json olarak aşağıdaki gibi dönülmüştür.
- getTournamentData() methodu ile belli bir turnuvaya ait detay bilgisi, geri dönülmektedir. Burada önemli bir husus vardır.
- İlgili ID’ye ait data önceden çekilmiş ve “forceRefresh” değişkeni false ise, kayıt birdaha servisden çekilmez ve offline olarak önceden tanımlı “tourneyData [ ]” dizisinden alınır.==>”this.currentTournament = this.tourneyData[ID]“. Bunu yapmakta tek amaç tabi ki performansdır.
- “forceRefresh” : İlerde bahsedilecek olan, sayfa refresh olmaya zorlanmış ise, yani aşağıda görüldüğü gibi pull request yapılarak sayfa en üstte iken aşağı doğru çekilerek yenilenmesi sağlanmış is, true değeri atanır. Böyle bir durumda her zaman en güncel data servisden çekilerek, bu güncel datanın ekrana basılması sağlanır.
- En son data bu Url’den çekilir ==>”this.http.get(
${this.baseUrl}/tournaments-data/${ID}.json
)“ - Servisden dönen cevap offline olarak “this.tourneyData[ ]” dizisine konur==> “this.tourneyData[ID] = result.json();” Böylece ilerde sayfa, refreshe zorlanmadan bir istek gelir ise, data hiç servise gitmeden offline olarak verilir. Bu da performansda büyük bir artışa sebep olur. Bu yöntemin sadece belli datalarda, örneğin ana sayfadaki datada yapılması önemlidir. Her yerde kullanılır ise telefonun kısıtlı memory kaynakları doldurulmuş olunur.
- En son data bu Url’den çekilir ==>”this.http.get(
- refreshCurrentTourney(): Pull Request yapıldığında çağrılacak olan methoddur.
- getCurrentTourney(): Belli bir ID’ye göre çağrılan geçerli Turnuva bilgisidir. Takımlardan birinin detayına “team-detail” sayfasına Anayfadaki favoriler tıklanarak gidildiğinde, hatırlarsanız önce “getTournamentData()” methodu çağrılmakta ve aşağıda görülen, “this.currentTournament” [ ] dizisi doldurulmakta idi. İşte “getCurrentTourney()” methodu sayesinde, ilgili değişken “team-detail” sayfasından çağrılır.
elite-api.service.ts:
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 |
import { Injectable } from '@angular/core'; import { Http } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/catch'; import 'rxjs/add/observable/of'; @Injectable() export class EliteApi { private baseUrl = "https://elite-schedule-app-1bbf9.firebaseio.com"; currentTournament: any = {}; private tourneyData = {}; constructor(private http: Http) { } getTournaments() { return this.http.get(`${this.baseUrl}/tournaments.json`) .map(result => result.json()); } getTournamentData(ID, forceRefresh: boolean = false): Observable<any> { if (!forceRefresh && this.tourneyData[ID]) { this.currentTournament = this.tourneyData[ID]; console.log("no need to make HTTP call"); return Observable.of(this.currentTournament); } console.log("about to to make HTTP call"); return this.http.get(`${this.baseUrl}/tournaments-data/${ID}.json`) .map(result => { this.tourneyData[ID] = result.json(); this.currentTournament = this.tourneyData[ID]; return this.currentTournament; }); } refreshCurrentTourney() { return this.getTournamentData(this.currentTournament.tournament.id, true); } getCurrentTourney() { return this.currentTournament; } } |
user-settings.service.ts: Bu sınıf da tamamen kullanıcının kenidine göre uygulamayı özelleştirmesi ve favori takım işlemleri için oluşturulmuş olan, ilerde kullanılacak bir servisdir.
Ionic local Storage: Amaç performans, hız ve internet ihtiyacını minimuma indirmektir.
- favoriteTeam() : Bu method ilerde anlatılacak bir sayfada çağrılacaktır. Öncelikle item sınıfı oluşturulur. “this.storage.set()” methodu ile favori takımlar localde saklanır. Key olarak team.id, value olarak json string item nesnesi tutulmaktadır. Peki burada amaç nedir? Amaç, her kullanıcı uygulamada ilk olarak ana sayfadan başlıyacaktır. Ve favori takım bilgileri bu ana sayfada listelenecektir. İşte bu da yüksek trafikte, büyük bir yük demektir. Her bir kullanıcaya ait favori takım bilgisini servisler ile bir DB’den çekmekten ise localden almak hem sunucu üzerindeki yükü azaltacak, hem de ilgili takımların hızlı bir şekilde ekrana dolmasını sağlayacaktır. Son olarak en önemlisi client’ın internet kotasını yemiyecek olmasıdır :) İşin şakası bir yana, her yerde tabi ki local storage kullanılmamalıdır. Aksi takdirde, zaten kısıtlı kaynakları olan cihazın kapasitesi, gereksiz yere doldurulmuş olacaktır.
Ionic Pub/Sub Event :
- this.events.publish(‘favorites:changed’): Burada, önceki makalede de bahsedilen “app.component” sayfasının “Pub/Sub” yani event kısmında, “favorites:changed” kanalının dinlediği “subscribe” methodu tetiklenir. Peki burada amaçlanan nedir? Esas amaç menü olarak kullanılan “app.component.html“, uygulamanın life cycle’ı boyunca sadece ilk açıldığında bir kerelik yüklenir. Bir daha uygulama boyunca yükleme işlemi olmaz. Favori takımlar menü sayfasında da bulunmaktadır. Kullanıcı yeni bir takımı favorilerine kattığı zaman, şu anda incelediğimiz servisdeki “favoriteTeam()” methodu çağrılır. İşte tam bu noktada favorilere eklenen yeni takım bilgisi, favorilerin geçtiği menü sayfasında da güncellenmelidir. Burada imdadımıza “Pub/Sub” trigger teknolojisi devreye girmektedir.
- unfavoriteTeam() : Favorilerden çıkarılan takım bilgisi storage’dan da, “this.storage.remove()” methodu ile kaldırılır. Ayrıca “favorites:changed” kanalının dinlediği “subscribe” burada da tetiklenir. Ve böylece ilgili takım menu’den, yani “app.component.html” sayfasından da kaldırılır.
- isFavoriteTeam() : Yine ilerde kullanılacak olan “storage.get()” methodu ile ilgili “team.id”‘ye göre check edilen takımın favorilerde olup olmadığına localden bakılır. Bunun ne için kullanılacağı bir sonraki makalede incelenecektir.
- getAllFavorites(): Storage’daki tüm favori takım bilgileri gezilip, value değerleri “Json.parse()” edilip, bir “items[ ]” dizisine doldurularak geri dönülmektedir. Bu method, bir önceki makalede de anlatıldığı gibi “app.component.ts” ve “my-teams.ts” sayfalarında bulunan favori takımların datasını yenilemek için kullanılmıştır.
user-settings.service.ts:
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 |
import { Injectable } from '@angular/core'; import { Storage } from '@ionic/storage'; import * as _ from 'lodash'; import 'rxjs/add/operator/toPromise'; import { Events } from "ionic-angular"; @Injectable() export class UserSettings { constructor(private storage: Storage,private events:Events) { } favoriteTeam(team, tournamentId, tournamentName) { let item = { team: team, tournamentId: tournamentId, tournamentName: tournamentName }; this.storage.set(String(team.id), JSON.stringify(item)); this.events.publish('favorites:changed'); } unfavoriteTeam(team) { this.storage.remove(String(team.id)); this.events.publish('favorites:changed'); } isFavoriteTeam(teamId) { return this.storage.get(String(teamId)).then(value => value ? true : false); } getAllFavorites() { let items = []; this.storage.forEach((value, key, index) => { items.push(JSON.parse(value)); }).then(() => { return items }); return items; } } |
Bu makalede LocalStorage, Pub/Sub ve Direction gibi önemli konulara detaylıca değinilmiştir. Ayrıca Ionic Lifecycle ile mobile uygulamada olan çalışma adımları, Loading Controller ile sayfalar arası geçiş ve servisden cevap beklenirken, yükleniyor ibaresinin gösterilmesi, force refresh konusuna giriş ve servis mimarisi bu yazıda anlatılan ana konu başlıklarıdır.
Bir sonraki makalede Takım Detay sayfasında, ilgili takımı favorilere ekleyerek pub/sub konusunu detaylıca değinilecektir. “Ion-Tabs” leri inceleyip, Lodash kütüphanesi ile tarihe göre nasıl filitreleme yapılacağını hep beraber göreceğiz. ToastController ile sayfanın altından nasıl bildirimler çıkarılacağını ve confirm alert pencerelerini inceleyeceğiz. En önemli konuyu sona bırakarak Takım Detayda, “Team” tab’ine basıldığında sıralanan takımlar sayfasını ve IONIC-SEARCH işlemini detaylıca inceleyeceğiz.
Bir sonraki makalede görüşmek üzere hoşçakalın.
Kaynaklar: Pluralsight Steve Michelotti, https://ionicframework.com, https://github.com/ionic-team/ionic, Gaurav Saini’nin “Hybrid Mobile Development with Ionic” kitabından faydalandım.
Son Yorumlar