Apa Itu API Gateway ?

API Gateway merupakan gerbang dari beberapa API, bertugas sebagai management API, merge beberapa API, authentication API dan lain - lain.

Berbicara mengenai API gateway maka tidak terlepas dengan pembahasan microservice. Microservice itu sendiri sebenarnya adalah kumpulan dari beberapa service atau kumpulan dari beberapa API. API ini biasanya berbentuk REST API menggunakan protokol http. Microservice sendiri sebenarnya berasal dari monolithic yang telah dipecah. Monolithic adalah aplikasi yang sehari - hari kita kembangkan, dimana semua module terdapat di dalam 1 aplikasi yang sangat besar.

Client biasanya hanya mengakses 1 URL REST API sedangkan microservice memiliki banyak service REST API, untuk mengatasi masalah ini maka kita gunakan API gateway. Terdapat banyak contoh API gateway yang ada di pasaran contohnya kong, tyk, API Umbrella dan lain - lain.

Bagaimana arsitektur yang pernah saya gunakan ?

Biasanya saya akan membuat microservice dengan stack spring framework :). Mengapa menggunakan spring framework ? ya alasan nya karena framework ini merupakan framework yang sudah memiliki banyak support, terutama dalam hal membuat REST API, authentication dengan OAuth2, session dan lain - lain. Berikut adalah arsitektur yang pernah saya gunakan untuk membuat microservice dan API gateway sederhana.

API Gateway.svg

Dari gambar diatas dapat dilihat bahwa aplikasi yang dibuat sangatlah besar meskipun hanya membuat service buku dan review buku. Pada gambar diatas terdapat :

  • database redis : ini kita gunakan untuk menyimpan session
  • database authentication : database untuk menyimpan data user, client credenetial dan lain - lain
  • database book : database untuk menyimpan data - data katalog buku
  • database review : database untuk menyimpan data - data review dari sebuah buku
  • authorization server : adalah authorization server yang berfungsi sebagai management otorisasi setiap service REST API. Authorization yang saya gunakan adalah berbasis OAuth2, dimana antar service wajib melakukan authentication terlebih dahulu sebelum mengakses service yang lain, contohnya adalah ketika service book ingin mengakses service review maka service book diharuskan login terlebih dahulu dengan menggunakan authentication OAuth2 grant type client credentials. Tidak hanya antar service, API gateway yang akan mengakses suatu service juga wajib melakukan authentication terlebih dahulu, akan tetapi setiap service memiliki client id, client secret dan hak akses masing - masing sehingga memiliki keterbatasan dalam mengakses masing - masing service. Bagi anda yang masih bingung dengan OAuth2, silahkan baca artikel Belajar OAuth2.
  • service book : REST API penyedia book
  • service review : REST API penyedia review book
  • API Gateway : yang bertugas untuk management API. Biasanya saya akan melakukan merge beberapa request API jika client membutuhkan banyak API. Contohnya jika kita membutuhkan API yang berisi data book beserta review nya, maka kita harus melakukan request sebanyak 2x yaitu melakukan request ke service book lalu request kembali ke service review. Biasanya model seperti ini akan membuat banyak latency di bagian client, nah untuk mengurangi hal tersebut maka kita cukup melakukan merge request API di bagian API gateway sehingga client hanya perlu melakukan request sekali ke API gateway, biasanya saya akan melakukan merge request API dengan menggunakan RxJava, contohnya adalah seperti berikut.
fun getBookDetails(bookId: Long): Observable<BookDetail> {
    return Observable.zip(
        catalogService.getBook(bookId),
        reviewService.getReviews(bookId),
        this::buildBookDetails
    )
}

private fun buildBookDetails(book: Book, reviews: Iterable<Review>): BookDetail {
    return BookDetail(
            id = book.id,
            title = book.title,
            description = book.description,
            reviews = reviews
    )
}

source code diatas ada disini

Mungkin ada yang bertanya - tanya, mengapa menggunakan redis ?, yups untuk menyimpan sebuah session yang berupa token, terdapat beberapa pendekatan yaitu kita dapat menyimpan token tersebut pada database atau dapat juga disimpan ke dalam redis. Jika menggunakan database maka database nya harus disharing antar service, ini mungkin pekerjaan yang lumayan merepotkan jika kita memiliki banyak service. Alternatif lain adalah menggunakan redis, dimana redis ini akan kita buat di 1 server sendiri.

Bagaimana Menyimpan Session Pada Redis ?

Nah lagi - lagi spring menyediakan kebutuhan session, di spring ada namanya spring-session, dimana kita dapat menyimpan session langsung ke redis. Lalu bagaimana dengan konfigurasi OAuth2 ? apakah token nya dapat disimpan ke redis ? jawaban nya adalah sangatlah mungkin, cara nya adalah dengan menggunakan token store seperti berikut.

@Bean
fun tokenStore(): TokenStore {
    return RedisTokenStore(jedisConnectionFactory)
}

@Throws(Exception::class)
override fun configure(authorizationServerEndpointsConfigurer: AuthorizationServerEndpointsConfigurer?) {
    authorizationServerEndpointsConfigurer
        ?.accessTokenConverter(jwtAccessTokenConverter())
        ?.tokenStore(tokenStore())
        ?.authenticationManager(authenticationManager)
}

source code diatas ada disini

Sehingga apabila anda berhasil melakukan authentikasi, maka token anda akan disimpan ke redis, ingat bahwa token ini memiliki masa nya tersendiri sehingga hanya dapat digunakan untuk waktu tertentu saja.

Bagaimana service lain seperti service book dan service review tau bahwa token yang dikirim oleh client adalah token hasil yang digenerate oleh authorization server ?

Yups lagi - lagi kita menggunakan fungsi spring-session untuk menghandle masalah seperti ini, di bagian service - service kita hanya perlu mendeklarasikan resource id dan juga konfigurasi OAuth2 seperti berikut.

@Bean
fun tokenStore(): TokenStore {
    return RedisTokenStore(jedisConnectionFactory)
}

override fun configure(resourceServerSecurityConfigurer: ResourceServerSecurityConfigurer?) {
    resourceServerSecurityConfigurer
        ?.tokenStore(tokenStore())
        ?.resourceId(RESOURCE_ID)
}

source code diatas ada disini

CATATAN : setiap service wajib memiliki 1 resource id, dimana resource id ini akan didaftarkan ke authorization server. Pencatatan resource id ini berfungsi sebagai memberikan batasan kepada client tertentu, sehingga client tertentu hanya boleh mengakses resource tertentu, contohnya adalah jika client seperti mobile atau web langsung mengakses service book maka tidak diperbolehkan dikarenakan client seperti mobile atau web tidak memiliki resource id dari service book, yang memiliki resource id service book adalah API gateway, sehingga client seperti mobile atau web hanya bisa mengakses melalui API gateway.

Untuk source code diatas dapat anda akses di Simple API Gateway. Sekian artikel mengenai Belajar API Gateway, jika ada saran dan komentar silahkan isi dibawah dan terima kasih :)

Akhir nya kita bertemu lagi di artikel bagian 4 untuk development iOS :). Artikel ini adalah artikel bagian terakhir untuk pembahasan mengenai development iOS. Pada artikel ini, kita hanya akan membahas bagaimana cara memutar music yang berasal dari API spotify, ingat bahwa lagu yang akan diputar adalah hanya preview lagu saja, jadi lagu yang diputar tidak nya lengkap :).

Oke, silahkan buka project anda dengan xcode, nah disini kita perlu terlebih dahulu memisahkan antara view dan controller. Silahkan buat sebuah group dengan nama controllers, silahkan buka group views, disana terdapat file TrackTableView silahkan buka file tersebut, lalu copy isi source code nya lalu hapus file tersebut, silahkan buat file kembali dengan nama TrackTableView di dalam group controller dan paste kan isinya. Oke setelah selesai memindahkan nya, tahap selanjutnya silahkan buat sebuah file swift kembali di dalam group controllers dengan file TrackPreviewViewController.

Langkah selanjutnya kita akan terlebih dahulu membuat view, silahkan buat sebuah story board di dalam group views dengan nama TrackPreview, lalu untuk desainnya silahkan lihat video berikut.

Nah tahap desain udah selesai :D. Selanjutnya silahkan buka kembali file TrackPreviewViewController lalu isikan codingan seperti berikut.

//
//  TrackPreviewViewController.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 5/11/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import UIKit
import AudioPlayerManager
import Kingfisher
import MarqueeLabel

class TrackPreviewViewController: UIViewController {

    @IBOutlet weak var buttonPlayPause: UIButton!
    @IBOutlet weak var labelArtist: MarqueeLabel!
    @IBOutlet weak var labelTrack: MarqueeLabel!
    @IBOutlet weak var imageAlbum: UIImageView!
    @IBOutlet weak var imageBackground: UIImageView!
    
    let track = UserDefaults.standard.object(forKey: "trackPreview") as! String
    let artistsName = UserDefaults.standard.object(forKey: "trackName") as! String
    let trackName = UserDefaults.standard.object(forKey: "artistName") as! String
    let image = UserDefaults.standard.object(forKey: "imageAlbum") as! String
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.title = "Belajar iOS"
        
        self.labelArtist.type = .continuous
        self.labelArtist.speed = .duration(15)
        self.labelArtist.animationCurve = .easeInOut
        
        self.labelTrack.type = .continuous
        self.labelTrack.speed = .duration(15)
        self.labelTrack.animationCurve = .easeInOut
        
        self.labelArtist.text = artistsName
        self.labelTrack.text = trackName
        
        let url = URL(string: image)
        self.imageBackground.kf.setImage(with: url)
        self.imageAlbum.kf.setImage(with: url)
        
        if AudioPlayerManager.shared.isPlaying() == true {
            AudioPlayerManager.shared.stop()
        }
        AudioPlayerManager.shared.play(urlString: track)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    @IBAction func playButtonAction(_ sender: Any) {
        if AudioPlayerManager.shared.isPlaying() == true {
            AudioPlayerManager.shared.pause()
            self.buttonPlayPause.setImage(UIImage(named: "play-icon"), for: .normal)
        } else {
            AudioPlayerManager.shared.play()
            self.buttonPlayPause.setImage(UIImage(named: "pause-icon"), for: .normal)
        }
    }
}

Untuk memutar music, penulis menggunakan library AudioPlayerManager :). Nah untuk mengambil data dari storyboard sebelumnnya, kita menggunakan fungsi UserDefaults.standard, fungsi ini sama seperti fungsi LocalStorage pada web browser. Untuk memutar musik, kita dapat menggunakan fungsi AudioPlayerManager.shared.play, baik itu musik di local mauapun melalui url, pada contoh kali ini kita menggunakan url. Untuk menganti gambar pada button, kita hanya perlu melakuka set image seperti perintah berikut.

self.buttonPlayPause.setImage(UIImage(named: "play-icon"), for: .normal)

Sehingga pada saat musik sedang berjalan, maka posisi button adalah pause, sedangkan pada saat musik berhenti maka posisi button adalah play.

Oke langkah selanjutnya, bagaimana cara untuk berpindah dari 1 storyboard ke storyboard yang lain ?. Ada beberapa cara yaitu bisa menggunakan segue atau menggunakan pushViewController, biasanya saya akan menggunakan pushViewController. Silahkan buka file ViewController lalu pada bagian extension UITableViewDelegate tambahkan kodingan berikut.

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if indexPath.row == (self.rowData.count - 1) {
            if (self.nextPage ?? "").isEmpty == false {
                self.getDefaultTrack(track: track, offset: nextInt, limit: 20)
            }
        }
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if UserDefaults.standard.object(forKey: "trackPreview") != nil {
            UserDefaults.standard.removeObject(forKey: "trackPreview")
            UserDefaults.standard.synchronize()
        }
        if UserDefaults.standard.object(forKey: "trackName") != nil {
            UserDefaults.standard.removeObject(forKey: "trackName")
            UserDefaults.standard.synchronize()
        }
        if UserDefaults.standard.object(forKey: "artistName") != nil {
            UserDefaults.standard.removeObject(forKey: "artistName")
            UserDefaults.standard.synchronize()
        }
        if UserDefaults.standard.object(forKey: "imageAlbum") != nil {
            UserDefaults.standard.removeObject(forKey: "imageAlbum")
            UserDefaults.standard.synchronize()
        }

        UserDefaults.standard.set(self.rowData[indexPath.row].previewUrl, forKey: "trackPreview")
        UserDefaults.standard.set(self.rowData[indexPath.row].name, forKey: "trackName")
        UserDefaults.standard.set(ConvertString().fromArrayArtistToString(array: self.rowData[indexPath.row].artists!), forKey: "artistName")
        UserDefaults.standard.set(self.rowData[indexPath.row].album?.images?[0].url, forKey: "imageAlbum")
        UserDefaults.standard.synchronize()

        let storyboard = UIStoryboard(name: "TrackPreview", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "trackPreviewStoryBoard") as UIViewController
        self.navigationController?.pushViewController(vc, animated: true)
    }
}

fungsi dari function kedua diatas adalah jika user memilih salah satu row table, maka akan dijalankan aksi di dalam nya, di dalam function tersebut kita menyimpan data musik yang akan dikirim ke storyboard berikutnya, untuk berpindah ke storyboard kedua, kita menggunakan perintah berikut.

let storyboard = UIStoryboard(name: "TrackPreview", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "trackPreviewStoryboard") as UIViewController
self.navigationController?.pushViewController(vc, animated: true)

Pertama kita deklarasikan terlebih dahulu variabel storyboard dengan value nama storyboard, tadinya kita membuat storyboard dengan nama TrackPreview, lalu buat sebuah variabel dengan menampung id dari storyboard tersebut, id ini bersifat unik walaupun berbeda storyboard, silahkan lihat video diatas pada saat deklarasikan storyboard id, storyboard id nya yaitu trackPreviewStoryboard. Selanjutnya untuk menampilkan storyboard berikutnya, kita menggunakan perintah pushViewController sehingga storyboard kedua akan berada diatas storyboard pertama sehingga akan saling tumpang tindih, konsep ini sebenarnya sama seperti konsep intent pada android :).

Berikut kodingan lengkap untuk ViewController

//
//  ViewController.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 4/30/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import UIKit
import RxSwift
import Kingfisher
import SwiftOverlays

class ViewController: UIViewController {

    @IBOutlet weak var trackTable: UITableView!
    let searchBar = UISearchBar()

    var rowData: [Item] = []
    var nextPage: String? = nil
    var nextInt = 0
    var limitInt = 20
    var track = "ColdPlay"
    var isSearch = false

    private var disposeBag = DisposeBag()
    private var trackService = TrackService()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "Belajar iOS"
        self.getDefaultTrack(track: track, offset: nextInt, limit: limitInt)
        self.trackTable.delegate = self
        self.trackTable.dataSource = self

        self.searchBar.placeholder = "Cari"
        self.searchBar.delegate = self
        self.searchBar.tintColor = UIColor.black
        self.searchBar.sizeToFit()
        self.navigationItem.titleView = searchBar
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func getDefaultTrack(track: String, offset: Int, limit: Int) {
        SwiftOverlays.showBlockingWaitOverlay()
        trackService
            .getTrackByQuery(parameters: ConvertString().toPlusString(text: track), offset: offset, limit: limit)
            .subscribe(
                onNext: { trackResponse in
                    if offset == 0 && trackResponse.tracks?.items?.count != 0 {
                        self.rowData.removeAll()
                    }
                    if trackResponse.tracks?.items?.count != 0 {
                        self.rowData.append(contentsOf: (trackResponse.tracks?.items)!)
                        if ((trackResponse.tracks?.next) ?? "").isEmpty == false {
                            self.nextPage = (trackResponse.tracks?.next)!
                        } else {
                            self.nextPage = nil
                        }
                        self.nextInt = (trackResponse.tracks?.offset)! + 20
                        self.trackTable.reloadData()
                    } else {
                        let alert = UIAlertController(title: "Info", message: "Maaf, Data Tidak ditemukan :(", preferredStyle: UIAlertControllerStyle.alert)
                        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
                        self.present(alert, animated: true, completion: nil)
                    }
                    SwiftOverlays.removeAllBlockingOverlays()
                },
                onError: { error in
                    SwiftOverlays.removeAllBlockingOverlays()
                }
            )
            .addDisposableTo(disposeBag)
    }

}

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if indexPath.row == (self.rowData.count - 1) {
            if (self.nextPage ?? "").isEmpty == false {
                self.getDefaultTrack(track: track, offset: nextInt, limit: 20)
            }
        }
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if UserDefaults.standard.object(forKey: "trackPreview") != nil {
            UserDefaults.standard.removeObject(forKey: "trackPreview")
            UserDefaults.standard.synchronize()
        }
        if UserDefaults.standard.object(forKey: "trackName") != nil {
            UserDefaults.standard.removeObject(forKey: "trackName")
            UserDefaults.standard.synchronize()
        }
        if UserDefaults.standard.object(forKey: "artistName") != nil {
            UserDefaults.standard.removeObject(forKey: "artistName")
            UserDefaults.standard.synchronize()
        }
        if UserDefaults.standard.object(forKey: "imageAlbum") != nil {
            UserDefaults.standard.removeObject(forKey: "imageAlbum")
            UserDefaults.standard.synchronize()
        }

        UserDefaults.standard.set(self.rowData[indexPath.row].previewUrl, forKey: "trackPreview")
        UserDefaults.standard.set(self.rowData[indexPath.row].name, forKey: "trackName")
        UserDefaults.standard.set(ConvertString().fromArrayArtistToString(array: self.rowData[indexPath.row].artists!), forKey: "artistName")
        UserDefaults.standard.set(self.rowData[indexPath.row].album?.images?[0].url, forKey: "imageAlbum")
        UserDefaults.standard.synchronize()

        let storyboard = UIStoryboard(name: "TrackPreview", bundle: nil)
        let vc = storyboard.instantiateViewController(withIdentifier: "trackPreviewStoryboard") as UIViewController
        self.navigationController?.pushViewController(vc, animated: true)
    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.rowData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = Bundle.main.loadNibNamed("TrackTableViewCell", owner: self, options: nil)?.first as! TrackTableViewCell

        cell.trackNameLabel.text = self.rowData[indexPath.row].name
        cell.artistNameLabel.text = ConvertString().fromArrayArtistToString(array: self.rowData[indexPath.row].artists!)
        cell.albumNameLabel.text = self.rowData[indexPath.row].album?.name

        let url = URL(string: (self.rowData[indexPath.row].album?.images?[0].url)!)
        cell.gambarAlbumImage.kf.indicatorType = .activity
        cell.gambarAlbumImage.kf.setImage(with: url)

        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 98
    }
}

extension ViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        self.track = self.searchBar.text!
        self.nextInt = 0
        self.getDefaultTrack(track: self.track, offset: self.nextInt, limit: self.limitInt)
        self.searchBar.resignFirstResponder()
        self.searchBar.setShowsCancelButton(false, animated: true)
        self.searchBar.resignFirstResponder()
    }

    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        self.searchBar.setShowsCancelButton(true, animated: true)
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        self.searchBar.text = nil
        self.searchBar.setShowsCancelButton(false, animated: true)
        self.searchBar.resignFirstResponder()
    }
}

Sekarang silahkan jalankan, berikut adalah hasil output nya

Screen Shot 2017-05-11 at 3.53.33 PM.png

Wah tombol back nya kurang bagus nih :(, oke kita akan coba ganti tulisan nya menjadi kembali dengan warna hitam, silahkan buka file TrackPreviewViewController lalu tambahkan kodingan berikut di function viewDidLoad.

self.navigationController?.navigationBar.tintColor = UIColor.black
self.navigationController?.navigationBar.topItem?.title = "Kembali"

dan hasilnya seperti berikut :D.

Akhirnya selesai juga artikel mengenai belajar iOS dari bagian 1 hingga 4 :). Untuk source code diatas dapat anda akses di Belajar-iOS. Sekian artikel mengenai Belajar iOS bagian 4, jika ada saran dan komentar silahkan isi dibawah dan terima kasih :)

Sebelumnya kita telah membuat aplikasi untuk melihat list musik, akan tetapi kurang jika kita tidak menambahkan fitur search :). Oke, pada artikel ini kita akan membahas mengenai bagaimana cara membuat search :D. Mungkin artikel ini adalah artikel yang terpendek dikarenkan untuk membuat sebuah search bar di swift sangatlah gampang :). Oke kita langsung buka project iOS dengan xcode, project yang kita gunakan adalah project sebelum nya yang ada di artikel sebelumnya.

Untuk membuat search bar silahkan buka file ViewController, kemudian deklarasikan sebuah variabel searchBar seperti berikut.

let searchBar = UISearchBar()

Setelah selesai, kita akan membuat search bar tersebut tepat di navigation menu, silahkan masukkan codingan berikut tepat di dalam function viewDidLoad

self.searchBar.placeholder = "Cari"
self.searchBar.delegate = self
self.searchBar.tintColor = UIColor.black
self.searchBar.sizeToFit()
self.navigationItem.titleView = searchBar

Nah karena diatas ada perintah delegate maka kita harus membuat 1 extension untuk menghadle action dari search bar, silahkan masukkan codingan extension berikut di bagian paling bawah.

extension ViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        self.track = self.searchBar.text!
        self.nextInt = 0
        self.getDefaultTrack(track: self.track, offset: self.nextInt, limit: self.limitInt)
        self.searchBar.resignFirstResponder()
        self.searchBar.setShowsCancelButton(false, animated: true)
        self.searchBar.resignFirstResponder()
    }

    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        self.searchBar.setShowsCancelButton(true, animated: true)
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        self.searchBar.text = nil
        self.searchBar.setShowsCancelButton(false, animated: true)
        self.searchBar.resignFirstResponder()
    }
}

fungsi function searchBarSearchButtonClicked berfungsi untuk menghandle action ketika user menekan tombol search, dan pada saat itu, kita akan menjalankan perintah getDefaultTrack untuk mengambil data musik. function kedua berfungsi memunculkan tombol cancel ketika kita melakukan editing pada search bar nya dan pada function terakhir ketika user menekan tombol cancel maka tombol cancel akan kembali seperti biasa.

Berikut adalah hasil dari seluruh kodingannya.

//
//  ViewController.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 4/30/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import UIKit
import RxSwift
import Kingfisher
import SwiftOverlays

class ViewController: UIViewController {

    @IBOutlet weak var trackTable: UITableView!
    let searchBar = UISearchBar()

    var rowData: [Item] = []
    var nextPage: String? = nil
    var nextInt = 0
    var limitInt = 20
    var track = "ColdPlay"
    var isSearch = false

    private var disposeBag = DisposeBag()
    private var trackService = TrackService()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "Belajar iOS"
        self.getDefaultTrack(track: track, offset: nextInt, limit: limitInt)
        self.trackTable.delegate = self
        self.trackTable.dataSource = self

        self.searchBar.placeholder = "Cari"
        self.searchBar.delegate = self
        self.searchBar.tintColor = UIColor.black
        self.searchBar.sizeToFit()
        self.navigationItem.titleView = searchBar
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func getDefaultTrack(track: String, offset: Int, limit: Int) {
        SwiftOverlays.showBlockingWaitOverlay()
        trackService
            .getTrackByQuery(parameters: ConvertString().toPlusString(text: track), offset: offset, limit: limit)
            .subscribe(
                onNext: { trackResponse in
                    if offset == 0 && trackResponse.tracks?.items?.count != 0 {
                        self.rowData.removeAll()
                    }
                    if trackResponse.tracks?.items?.count != 0 {
                        self.rowData.append(contentsOf: (trackResponse.tracks?.items)!)
                        if ((trackResponse.tracks?.next) ?? "").isEmpty == false {
                            self.nextPage = (trackResponse.tracks?.next)!
                        } else {
                            self.nextPage = nil
                        }
                        self.nextInt = (trackResponse.tracks?.offset)! + 20
                        self.trackTable.reloadData()
                    } else {
                        let alert = UIAlertController(title: "Info", message: "Maaf, Data Tidak ditemukan :(", preferredStyle: UIAlertControllerStyle.alert)
                        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
                        self.present(alert, animated: true, completion: nil)
                    }
                    SwiftOverlays.removeAllBlockingOverlays()
                },
                onError: { error in
                    SwiftOverlays.removeAllBlockingOverlays()
                }
            )
            .addDisposableTo(disposeBag)
    }

}

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if indexPath.row == (self.rowData.count - 1) {
            if (self.nextPage ?? "").isEmpty == false {
                self.getDefaultTrack(track: track, offset: nextInt, limit: 20)
            }
        }
    }
}

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.rowData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = Bundle.main.loadNibNamed("TrackTableViewCell", owner: self, options: nil)?.first as! TrackTableViewCell

        cell.trackNameLabel.text = self.rowData[indexPath.row].name
        cell.artistNameLabel.text = ConvertString().fromArrayArtistToString(array: self.rowData[indexPath.row].artists!)
        cell.albumNameLabel.text = self.rowData[indexPath.row].album?.name

        let url = URL(string: (self.rowData[indexPath.row].album?.images?[0].url)!)
        cell.gambarAlbumImage.kf.indicatorType = .activity
        cell.gambarAlbumImage.kf.setImage(with: url)

        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 98
    }
}

extension ViewController: UISearchBarDelegate {
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
        self.track = self.searchBar.text!
        self.nextInt = 0
        self.getDefaultTrack(track: self.track, offset: self.nextInt, limit: self.limitInt)
        self.searchBar.resignFirstResponder()
        self.searchBar.setShowsCancelButton(false, animated: true)
        self.searchBar.resignFirstResponder()
    }

    func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
        self.searchBar.setShowsCancelButton(true, animated: true)
    }

    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        self.searchBar.text = nil
        self.searchBar.setShowsCancelButton(false, animated: true)
        self.searchBar.resignFirstResponder()
    }
}

Nah setelah selesai, silahkan jalankan aplikasi kalian, berikut adalah demo aplikasi nya :)

Artikel selanjutnya akan membahas mengenai bagaimana cara untuk memutar lagu :D. Untuk source code diatas dapat anda akses di Belajar-iOS. Sekian artikel mengenai Belajar iOS bagian 3, jika ada saran dan komentar silahkan isi dibawah dan terima kasih :)

Hy jumpa lagi :D, artikel ini merupakan kelanjutan dari artikel sebelumnya. Pada bagian kedua ini kita akan mencoba membuat hingga tahap menampilkan data :). Berikut adalah tahapan development yang akan kita lakukan.

  • Apa Itu Storyboards, xib dan auto layout ?
  • Membuat View Dan Navigation
  • Membuat Service Dengan RxSwift Dan Alamofire
  • Membuat Helper Dan Controller

Apa Itu Storyboards, XIB dan Auto Layout ?

Storyboard merupakan salah satu fitur untuk melakukan desain aplikasi. Pada zaman dulu, developer hanya dapat menggunakan fitur XIB. Storyboard dan XIB sama - sama mempunyai fungsi untuk melakukan desain interface aplikasi, akan tetapi pada zaman sekarang developer apple merekomendasikan untuk menggunakan storyboard. Di dalam story board kita dapat menggunakan beberapa layer aplikasi seperti gambar berikut.

Screen Shot 2017-04-30 at 9.50.18 PM.png

Sehingga anda dapat mengetahui alur dari aplikasi tersebut secara detail. Berbeda dengan XIB, di dalam xib kita hanya dapat menggunakan 1 layer aplikasi saja seperti gambar berikut.

Screen Shot 2017-04-30 at 9.52.35 PM.png

Akan tetapi, ketika dihadapkan dengan suatu project besar maka kedua nya akan tetap bisa kita gunakan karena masing - masing dari mereka berdua mempunyai kelebihan dan kekurangan masing - masing misalnya jika kita menggunakan 1 storyboard dengan banyak layer maka proses membuka storyboard tersebut akan memakan waktu lama disebabkan banyak nya layer di dalam nya.

Bagaimana dengan auto layout ?

Auto Layout biasanya digunakan jika kita akan mendevelop aplikasi dengan berbagai ukuran layar

Biasanya kita tidak hanya terpaku di 1 ukuran layar saja, kita diharuskan untuk membuat untuk beberapa ukuran layar maka kita dapat menggunakan fitur auto layout. Fitur auto layout ini sangat mirip dengan fitur constraint nya android. Berikut adalah percobaan untuk membuat auto layout.

Membuat Contoh Auto Layout

Silahkan buat sebuah project, lalu silahkan drag button dari menu kanan bawah seperti berikut.

Screen Shot 2017-04-30 at 9.59.24 PM.png

Setelah selesai, silahkan klik menu berikut lalu sesuaikan property nya seperti berikut.

Screen Shot 2017-04-30 at 10.03.16 PM.png

dan kemudian pilih add 4 constrains. Maka secara otomatis button kalian akan seperti berikut.

Screen Shot 2017-04-30 at 10.04.59 PM.png

Gambar diatas telah diubah warna backgroud dan warna tulisan nya, untuk mengubahnya, kamu dapat menggunakan menu seperti gambar berikut.

Screen Shot 2017-04-30 at 10.06.40 PM.png

Silahkan dijalankan di beberapa emulator untuk melihat hasil nya :).

Membuat View Dan Navigation

Oke, kembali ke project yang akan kita buat. Pada bagian ini kita akan membuat view dan navigation view. Secara default project anda hanya memiliki 1 storyboard yaitu main.storyboard. Silahkan buka storyboard tersebut, lalu pilih view controller nya, pada menu bagian atas silahkan pilih editor lalu pilih embed in dan terakhir pilih navigation controller maka akan muncul output seperti berikut.

Screen Shot 2017-04-30 at 10.34.18 PM.png

Setelah selesai, silahkan drag 1 table view ke view controller, lalu berikan constraint seperti berikut.

Screen Shot 2017-04-30 at 10.36.25 PM.png

Selanjutnya silahkan buat 1 group yaitu views, di dalam group views kita akan membuat view khusus untuk menampilkan list data lagu. Karena kita menggunakan table view, maka kita membutuhkan table view cell untuk list cell nya. Untuk membuat table view cell, silahkan klik kanan pada group views, lalu pilih new file, lalu pilih cocoa touch class, lalu klik next dan isikan property nya seperti berikut.

Screen Shot 2017-04-30 at 10.40.55 PM.png

Maka akan muncul 2 file yaitu TrackTableViewCell.swift dan TrackTableViewCell.xib. Silahkan buka yang xib lalu silahkan membuat desain seperti berikut.

Screen Shot 2017-04-30 at 10.44.49 PM.png

Setelah selesai, silahkan ikuti tutorial berikut untuk konfigurasi dari view table cell ke controller table cell.

Tahap selanjutnya, silahkan ikuti tutorial berikut untuk main storyboard.

Membuat Service Dengan RxSwift Dan Alamofire

Pada bagian ini, kita akan membuat service. Biasanya service disini saya gunakan untuk melakukan request data ke API. Untuk melakukan request data, kita akan menggunakan Alamofire. Untuk melakukan parsing json ke object swift biasanya saya menggunakan ObjectMapper. Untuk membuat aplikasi suatu event request yang asynchronous maka kita gunakan RxSwift, bagi anda yang ingin memperdalam tentang reactivex silahkan baca di ReactiveX.

Silahkan buat sebuah group dengan nama services lalu buatlah sebuah file swift dengan nama TrackService di dalam group service tersebut. Lalu isikan kodingan seperti berikut.

//
//  TrackService.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 4/30/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import Alamofire
import ObjectMapper
import AlamofireObjectMapper
import RxSwift

class TrackService {
    func getTrackByQuery(parameters: String, offset: Int, limit: Int) -> Observable<TrackResponse> {
        return Observable<TrackResponse>.create { observer -> Disposable in
            let request = Alamofire
                .request("https://api.spotify.com/v1/search?q=\(parameters)&type=track&offset=\(offset)&limit=\(limit)", method: .get)
                .validate()
                .responseObject(completionHandler: { (response: DataResponse<TrackResponse>) in
                    switch response.result {
                    case .success(let trackResponse):
                        observer.onNext(trackResponse)
                        observer.onCompleted()
                    case .failure(let error):
                        observer.onError(error)
                    }
                })
            return Disposables.create(with: {
                request.cancel()
            })
        }
    };
}

Dengan menggunakan RxSwift maka kita dapat menggunakan fitur Observable, fitur ini mirip dengan fitur List di java. Disana terdapat 3 parameter yaitu parameters biasanya ini untuk parameter yang akan kita search, offset sebagai halaman ke berapa karena pada api ini kita menggunakan paging, dan yang terakhir adalah limit yang berfungsi untuk mendeklarasikan berapa per halaman yang akan kita tampilkan.

Membuat Helper Dan Controller

Tahap terakhir kita akan membuat helper dan controller. Helper disini sendiri biasanya kita buat agar helper ini dapat digunakan di banyak tempat. Contohnya adalah, jika kita melakukan request, dimana jika kita menggunakan parameter dan parameter tersebut mempunyai spasi maka kita harus menconvert spasi dengan +, akan tetapi fungsi merubah karakter ini bergantung dari kebutuhan API, ada yang mengharuskan diubah menjadi + atau ada juga menggunakan %20 dan lain sebagainya.

Silahkan buat sebuah group dengan nama helpers lalu buat sebuah file swift dengan nama ConvertString, kemudian masukkan kodingan seperti berikut.

//
//  ConvertString.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 5/1/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import Foundation

class ConvertString {
    func toPlusString(text: String) -> String {
        return text.replacingOccurrences(of: " ", with: "+")
    }

    func fromArrayArtistToString(array: [Artist]) -> String {
        var text = ""
        if array.count == 1 {
            text = array[0].name!
        } else {
            for i in 0..<array.count {
                text = text + array[i].name! + ", "
            }
        }

        if text.substring(from: text.index(text.endIndex, offsetBy: -2)) == ", " {
            text.remove(at: text.index(text.endIndex, offsetBy: -2))
        }

        return text
    }
}

Pada class diatas terdapat 2 fungsi yaitu, fungsi toPlusString berfungsi untuk me replace string kosong dengan karakter +. Sedangkan fungsi fromArrayArtistToString kita gunakan untuk menconvert dari array object Artist menjadi string biasa.

Selanjutnya silahkan buka kembali file TrackTableViewCell lalu ubah codingan nya menjadi seperti berikut.

//
//  TrackTableViewCell.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 4/30/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import UIKit
import MarqueeLabel

class TrackTableViewCell: UITableViewCell {

    @IBOutlet weak var albumNameLabel: MarqueeLabel!
    @IBOutlet weak var artistNameLabel: MarqueeLabel!
    @IBOutlet weak var trackNameLabel: MarqueeLabel!
    @IBOutlet weak var gambarAlbumImage: UIImageView!

    override func awakeFromNib() {
        super.awakeFromNib()
        
        self.trackNameLabel.type = .continuous
        self.trackNameLabel.speed = .duration(15)
        self.trackNameLabel.animationCurve = .easeInOut
        
        self.artistNameLabel.type = .continuous
        self.artistNameLabel.speed = .duration(15)
        self.artistNameLabel.animationCurve = .easeInOut
        
        self.albumNameLabel.type = .continuous
        self.albumNameLabel.speed = .duration(15)
        self.albumNameLabel.animationCurve = .easeInOut
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
    }

}

Pada kodingan diatas, kita mendeklarasikan text marquee, tujuan nya adalah jika terdapat text yang panjang melebihi lebar view, maka secara otomatis text tersebut akan berjalan sehingga semua tulisan dapat anda baca. Setelah selesai, silahkan buka file ViewController lalu ubah kodingannya menjadi seperti berikut.

//
//  ViewController.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 4/30/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import UIKit
import RxSwift
import Kingfisher
import SwiftOverlays

class ViewController: UIViewController {

    @IBOutlet weak var trackTable: UITableView!

    var rowData: [Item] = []
    var nextPage: String? = nil
    var nextInt = 0
    var limitInt = 20
    var track = "ColdPlay"
    var isSearch = false

    private var disposeBag = DisposeBag()
    private var trackService = TrackService()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.title = "Belajar iOS"
        self.getDefaultTrack(track: track, offset: nextInt, limit: limitInt)
        self.trackTable.delegate = self
        self.trackTable.dataSource = self
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func getDefaultTrack(track: String, offset: Int, limit: Int) {
        SwiftOverlays.showBlockingWaitOverlay()
        trackService
            .getTrackByQuery(parameters: ConvertString().toPlusString(text: track), offset: offset, limit: limit)
            .subscribe(
                onNext: { trackResponse in
                    if offset == 0 && trackResponse.tracks?.items?.count != 0 {
                        self.rowData.removeAll()
                    }
                    if trackResponse.tracks?.items?.count != 0 {
                        self.rowData.append(contentsOf: (trackResponse.tracks?.items)!)
                        if ((trackResponse.tracks?.next) ?? "").isEmpty == false {
                            self.nextPage = (trackResponse.tracks?.next)!
                        } else {
                            self.nextPage = nil
                        }
                        self.nextInt = (trackResponse.tracks?.offset)! + 20
                        self.trackTable.reloadData()
                    } else {
                        let alert = UIAlertController(title: "Info", message: "Maaf, Data Tidak ditemukan :(", preferredStyle: UIAlertControllerStyle.alert)
                        alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
                        self.present(alert, animated: true, completion: nil)
                    }
                    SwiftOverlays.removeAllBlockingOverlays()
                },
                onError: { error in
                    SwiftOverlays.removeAllBlockingOverlays()
                }
            )
            .addDisposableTo(disposeBag)
    }

}

Karena kita membutuhkan delegate atau action untuk table, maka kita membutuhkan sebuah class extension, dimana class ini berfungsi untuk memisahkan fungsi implementasi class Protocol. Class Protocol ini sama seperti class interface yang ada di java. Silahkan tambahkan codingan extension berikut di akhir baris.

extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if indexPath.row == (self.rowData.count - 1) {
            if (self.nextPage ?? "").isEmpty == false {
                self.getDefaultTrack(track: track, offset: nextInt, limit: 20)
            }
        }
    }
}

Karena dia ingin menampilkan data maka kita juga membutuhkan dataSource, silahkan buat 1 extension lagi untuk dataSource seperti berikut.

extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.rowData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = Bundle.main.loadNibNamed("TrackTableViewCell", owner: self, options: nil)?.first as! TrackTableViewCell

        cell.trackNameLabel.text = self.rowData[indexPath.row].name
        cell.artistNameLabel.text = ConvertString().fromArrayArtistToString(array: self.rowData[indexPath.row].artists!)
        cell.albumNameLabel.text = self.rowData[indexPath.row].album?.name

        let url = URL(string: (self.rowData[indexPath.row].album?.images?[0].url)!)
        cell.gambarAlbumImage.kf.indicatorType = .activity
        cell.gambarAlbumImage.kf.setImage(with: url)

        return cell
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 98
    }
}

Function pertama berfungsi sebagai mendefinisikan jumlah data. Pada function kedua, kita melakukan load file xib, dimana file xib ini adalah table view cell, nah data nya akan kita input melalui parent class nya yaitu ViewController, dan pada function terakhir mendefinisikan tinggi dari xib.

Jika telah selesai, silahkan jalankan project nya, jika berhasil maka anda dapat melihat hasil nya seperti berikut :D.

Screen Shot 2017-05-01 at 1.16.04 AM.png

Artikel selanjutnya akan membahas mengenai bagaimana cara membuat search bar :). Untuk source code diatas dapat anda akses di Belajar-iOS. Sekian artikel mengenai Belajar iOS bagian 2, jika ada saran dan komentar silahkan isi dibawah dan terima kasih :)

Pada artikel sebelumnya penulis telah membahas mengenai bahasa pemrograman swift versi 3. Pada artikel ini, penulis akan membahas bagaimana cara membuat aplikasi iOS sederhana :).

Kebutuhan Project

Pada dasarnya, swift dapat dijalankan di linux dan OSX, akan tetapi ketika kita mulai ingin membangun sebuah aplikasi iOS maka kita membutuhkan sistem operasi OSX. Mengapa demikian ? karena untuk membuat aplikasi iOS membutuhkan IDE Xcode, mungkin ada yang bertanya, apakah kita dapat membuat aplikasi iOS tanpa Xcode ? ya bisa saja seperti menggunakan AppCode punya jetbrains, akan tetapi kita diharuskan semuanya untuk ngoding mulai dari logic hingga UI nya :(, berbeda dengan Xcode yang akan memberikan fitur UI layaknya android studio :D. Penulis menyarankan anda untuk menggunakan Xcode untuk development iOS, terdapat banyak fitur salah satunya adalah Xcode dapat memberitahukan kepada kita jika layout yang kita buat tidak responsive bahkan dia dapat mengetahui jika tata letak suatu komponent tidak sesuai dengan letaknya :). Wow… mungkin fitur Xcode bahkan bisa dibilang lebih lengkap dari android studio, oke berikut adalah kebutuhan project untuk development iOS.

  • MacBook / Laptop dengan Hackintosh, disarankan menggunakan OSX sierra
  • Xcode terbaru, pada artikel ini penulis menggunakan Xcode versi 8.3.2
  • secangkir kopi untuk bersantai :D

Tahap - Tahap Development

Kira - kira kita akan membuat apa ya ? ya, pada artikel ini, kita akan membuat sebuah aplikasi untuk menampilkan lagu - lagu yang ada di spotify :D. Bagaimana cara nya kita dapat mendapatkan datanya ? :o, gampang kok, kita hanya perlu mengakses API yang telah disediakan oleh spotify disini, karena kita hanya akan menampilkan item tertentu maka kita akan menggunakan API search yang ada pada spotify yaitu API yang ada disini. Pada API spotify, kita tidak diharuskan untuk login terkecuali jika berhubungan dengan data user :). Berikut adalah tahapan development yang akan kita lakukan.

  • Membuat Project iOS dengan Xcode
  • Melakukan Instalasi Dependency Dengan Cocoapods
  • Membuat Model Response
  • Membuat Service / Consume API
  • Membuat View Dengan Multi StoryBoard Dan Navigation
  • Membuat Controller
  • Uji Coba Aplikasi

Membuat Project iOS dengan Xcode

Hal yang pertama kita lakukan adalah membuat project dengan Xcode. Silahkan buka Xcode anda maka akan muncul tampilan seperti berikut.

Screen Shot 2017-03-31 at 10.29.11 PM.png

Lalu pilih create a new Xcode project maka akan muncul menu seperti berikut.

Screen Shot 2017-03-31 at 10.31.17 PM.png

Lalu pilih single view application lalu klik next dan muncul menu untuk pengisian deskripsi aplikasi, silahkan isi seperti berikut.

Screen Shot 2017-03-31 at 10.33.39 PM.png

Jika berhasil maka akan muncul menu project seperti berikut.

Screen Shot 2017-03-31 at 10.34.24 PM.png

Melakukan Instalasi Dependency Dengan Cocoapods

Terkadang pada sebuah project yang kita buat, kita membutuhkan dependency library dari pihak lain misalnya seperti Alamofire untuk consume API dan lain sebagainya. Setiap bahasa pemrograman mempunya tool tersendiri, begitu pula dengan swift, di swift kita dapat menggunakan Cocoapods sebagai dependency management karena Cocoapods salah satu tool yang sangat banyak digunakan oleh kalangan developer iOS.

Untuk melakukan instalasi Cocoapods, kita dapat menggunakan fungsi gem yang secara default telah tersedia di OSx, silahkan jalankan perintah berikut untuk melakukan instalasi cocoapods.

gem install cocoapods

Setelah selesai, silahkan close project Xcode anda, lalu akses folder project iOS anda dan jalankan perintah berikut untuk membuat konfigurasi cocoapods.

pod init

Maka akan terbentuk sebuah file yaitu Podfile langka selanjutnya silahkan buka file Podfile tersebut lalu silahkan ubah konfigurasi seperti berikut.

# Uncomment the next line to define a global platform for your project
 platform :ios, '10.0'

target 'Belajar-iOS' do
  # Comment the next line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for Belajar-iOS
  pod 'RxSwift',    '~> 3.0'
  pod 'RxCocoa',    '~> 3.0'
  pod 'ObjectMapper', '~> 2.2'
  pod 'Alamofire', '~> 4.4'
  pod 'AlamofireObjectMapper', '~> 4.0'
  pod 'Kingfisher', '~> 3.0'
  pod 'SwiftOverlays', '~> 3.0.0'
  pod 'AudioPlayerManager'
  pod 'MarqueeLabel/Swift'

  target 'Belajar-iOSTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'Belajar-iOSUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end

Berikut adalah beberapa penjelasan dari konfigurasi diatas :

  • platform : mendefiniskan versi iOS yang akan kita gunakan
  • RxSwift : berfungsi sebagai manipulasi UI Events dan untuk menghandle response API
  • RxCocoa : berfungsi sebagai library tambahan untuk mendukung RxSwift karena kita menggunakan Cocoapods
  • ObjectMapper : berfungsi untuk melakukan mapping json
  • Alamofire : berfungsi untuk mengakses API
  • AlamofireObjectMapper : berfungsi untuk mengubah response dari alamofire menjadi object swift
  • Kingfisher : berfungsi untuk cache gambar
  • SwiftOverlays : berfungsi untuk animasi loading
  • AudioPlayerManager : berfungsi untuk memutarkan lagu
  • MarqueeLabel : berfungsi untuk membuat text marquee

Oke, selanjutnya silahkan jalankan perintah berikut untuk melakukan instalasi dependency diatas.

pod install

Silahkan buka folder project anda, jika berhasil maka akan muncul sebuah workspace seperti berikut.

Screen Shot 2017-03-31 at 11.03.41 PM.png

Lalu silahkan klik kanan pada Belajar-iOS.xcworkspace tersebut lalu open with Xcode maka tampilan project anda akan seperti berikut.

Screen Shot 2017-03-31 at 11.06.02 PM.png

Membuat Model Response

Setelah selesai dengan dependency management, langkah selanjutnya kita akan membuat model response dimana sebenarnya model response ini adalah representasi dari response json yang berasal dari API. Berikut adalah contoh json yang dihasilkan dari API spotify.

{
  "tracks": {
    "href": "https://api.spotify.com/v1/search?query=Coldplay&type=track&offset=0&limit=20",
    "items": [
      {
        "album": {
          "album_type": "single",
          "artists": [
            {
              "external_urls": {
                "spotify": "https://open.spotify.com/artist/69GGBxA162lTqCwzJG5jLp"
              },
              "href": "https://api.spotify.com/v1/artists/69GGBxA162lTqCwzJG5jLp",
              "id": "69GGBxA162lTqCwzJG5jLp",
              "name": "The Chainsmokers",
              "type": "artist",
              "uri": "spotify:artist:69GGBxA162lTqCwzJG5jLp"
            },
            {
              "external_urls": {
                "spotify": "https://open.spotify.com/artist/4gzpq5DPGxSnKTe4SA8HAU"
              },
              "href": "https://api.spotify.com/v1/artists/4gzpq5DPGxSnKTe4SA8HAU",
              "id": "4gzpq5DPGxSnKTe4SA8HAU",
              "name": "Coldplay",
              "type": "artist",
              "uri": "spotify:artist:4gzpq5DPGxSnKTe4SA8HAU"
            }
          ],
          "available_markets": [
            "AD",
            "AR",
            "AT",
            "AU",
            "BE",
            "BG",
            "BO",
            "BR",
            "CA",
            "CH",
            "CL",
            "CO",
            "CR",
            "CY",
            "CZ",
            "DE",
            "DK",
            "DO",
            "EC",
            "EE",
            "ES",
            "FI",
            "FR",
            "GB",
            "GR",
            "GT",
            "HK",
            "HN",
            "HU",
            "ID",
            "IE",
            "IS",
            "IT",
            "JP",
            "LI",
            "LT",
            "LU",
            "LV",
            "MC",
            "MT",
            "MX",
            "MY",
            "NI",
            "NL",
            "NO",
            "NZ",
            "PA",
            "PE",
            "PH",
            "PL",
            "PT",
            "PY",
            "SE",
            "SG",
            "SK",
            "SV",
            "TR",
            "TW",
            "US",
            "UY"
          ],
          "external_urls": {
            "spotify": "https://open.spotify.com/album/7IzpJkWQqgz1BTutQvSitX"
          },
          "href": "https://api.spotify.com/v1/albums/7IzpJkWQqgz1BTutQvSitX",
          "id": "7IzpJkWQqgz1BTutQvSitX",
          "images": [
            {
              "height": 640,
              "url": "https://i.scdn.co/image/a57625bbecee7b4e7fe932a31cd92319d2a32855",
              "width": 640
            },
            {
              "height": 300,
              "url": "https://i.scdn.co/image/4c45b4ad2a79f5a345e854180cec249731db1908",
              "width": 300
            },
            {
              "height": 64,
              "url": "https://i.scdn.co/image/cd7345b44b0e950735295029bfd96b5d174bead6",
              "width": 64
            }
          ],
          "name": "Something Just Like This",
          "type": "album",
          "uri": "spotify:album:7IzpJkWQqgz1BTutQvSitX"
        },
        "artists": [
          {
            "external_urls": {
              "spotify": "https://open.spotify.com/artist/69GGBxA162lTqCwzJG5jLp"
            },
            "href": "https://api.spotify.com/v1/artists/69GGBxA162lTqCwzJG5jLp",
            "id": "69GGBxA162lTqCwzJG5jLp",
            "name": "The Chainsmokers",
            "type": "artist",
            "uri": "spotify:artist:69GGBxA162lTqCwzJG5jLp"
          },
          {
            "external_urls": {
              "spotify": "https://open.spotify.com/artist/4gzpq5DPGxSnKTe4SA8HAU"
            },
            "href": "https://api.spotify.com/v1/artists/4gzpq5DPGxSnKTe4SA8HAU",
            "id": "4gzpq5DPGxSnKTe4SA8HAU",
            "name": "Coldplay",
            "type": "artist",
            "uri": "spotify:artist:4gzpq5DPGxSnKTe4SA8HAU"
          }
        ],
        "available_markets": [
          "AD",
          "AR",
          "AT",
          "AU",
          "BE",
          "BG",
          "BO",
          "BR",
          "CA",
          "CH",
          "CL",
          "CO",
          "CR",
          "CY",
          "CZ",
          "DE",
          "DK",
          "DO",
          "EC",
          "EE",
          "ES",
          "FI",
          "FR",
          "GB",
          "GR",
          "GT",
          "HK",
          "HN",
          "HU",
          "ID",
          "IE",
          "IS",
          "IT",
          "JP",
          "LI",
          "LT",
          "LU",
          "LV",
          "MC",
          "MT",
          "MX",
          "MY",
          "NI",
          "NL",
          "NO",
          "NZ",
          "PA",
          "PE",
          "PH",
          "PL",
          "PT",
          "PY",
          "SE",
          "SG",
          "SK",
          "SV",
          "TR",
          "TW",
          "US",
          "UY"
        ],
        "disc_number": 1,
        "duration_ms": 247626,
        "explicit": false,
        "external_ids": {
          "isrc": "USQX91700278"
        },
        "external_urls": {
          "spotify": "https://open.spotify.com/track/1dNIEtp7AY3oDAKCGg2XkH"
        },
        "href": "https://api.spotify.com/v1/tracks/1dNIEtp7AY3oDAKCGg2XkH",
        "id": "1dNIEtp7AY3oDAKCGg2XkH",
        "name": "Something Just Like This",
        "popularity": 97,
        "preview_url": "https://p.scdn.co/mp3-preview/499eefd42a24ec562c464bd7acfad7ed41eb9179?cid=null",
        "track_number": 1,
        "type": "track",
        "uri": "spotify:track:1dNIEtp7AY3oDAKCGg2XkH"
      }
    ],
    "limit": 20,
    "next": "https://api.spotify.com/v1/search?query=Coldplay&type=track&offset=20&limit=20",
    "offset": 0,
    "previous": null,
    "total": 4618
  }
}

Kita tidak akan menggunakan semua value diatas, kita hanya akan menampilkan track, nama artist, nama album, judul album, gambar album dan akan memutarkan contoh lagu nya. Silahkan klik kanan pada Folder Belajar-iOS yang berwarna kuning lalu pilih New Group dan rename menjadi models. Setelah selesai, silahkan klik kanan pada group model lalu pilih menu New File, silahkan pilih menu Swift File, kemudian untuk menetukan direktory, anda harus membuat folder terlebih dahulu sehingga group models akan direpresentasikan ke folder models, berikan nama TrackResponse pada file swift yang anda buat, kemudian masukkan codingan seperti berikut.

//
//  TrackResponse.swift
//  Belajar-iOS
//
//  Created by rizki mufrizal on 4/30/17.
//  Copyright © 2017 rizki mufrizal. All rights reserved.
//

import Foundation
import ObjectMapper

struct TrackResponse: Mappable {
    var tracks: Track?

    init?(map: Map) {

    }

    mutating func mapping(map: Map) {
        tracks <- map["tracks"]
    }
}

struct Track: Mappable {
    var limit: Int?
    var next: String?
    var offset: Int?
    var previous: String?
    var total: Int?
    var items: [Item]?

    init?(map: Map) {

    }

    mutating func mapping(map: Map) {
        limit <- map["limit"]
        next <- map["next"]
        offset <- map["offset"]
        previous <- map["previous"]
        total <- map["total"]
        items <- map["items"]
    }
}

struct Item: Mappable {
    var name: String?
    var previewUrl: String?
    var album: Album?
    var artists: [Artist]?

    init?(map: Map) {

    }

    mutating func mapping(map: Map) {
        name <- map["name"]
        previewUrl <- map["preview_url"]
        album <- map["album"]
        artists <- map["artists"]
    }
}

struct Artist: Mappable {
    var name: String?

    init?(map: Map) {

    }

    mutating func mapping(map: Map) {
        name <- map["name"]
    }
}

struct Album: Mappable {
    var name: String?
    var images: [Image]?

    init?(map: Map) {

    }

    mutating func mapping(map: Map) {
        name <- map["name"]
        images <- map["images"]
    }
}

struct Image: Mappable {
    var height: Int?
    var width: Int?
    var url: String?

    init?(map: Map) {

    }

    mutating func mapping(map: Map) {
        height <- map["height"]
        width <- map["width"]
        url <- map["url"]
    }
}

Akhirnya selesai juga untuk class response nya :D. Artikel selanjutnya akan membahas mengenai bagaimana cara membuat service, view dan controller nya :). Untuk source code diatas dapat anda akses di Belajar-iOS. Sekian artikel mengenai Belajar iOS bagian 1, jika ada saran dan komentar silahkan isi dibawah dan terima kasih :)