let swift == val kotlin -> Why iOS is so important for Android, and vice versa

let swift == val kotlin -> Why iOS is so important for Android, and vice versa

Droidcon NYC 2018

C887ad592770a197f114d0a1d3e3a5a7?s=128

Jorge Coca

August 27, 2018
Tweet

Transcript

  1. let swift == val kotlin → WHY IOS IS SO

    IMPORTANT FOR ANDROID, AND VICE VERSA Jorge Coca
  2. LET'S BUILD A NEW startup

  3. la la land DROIDCON NYC EDITION

  4. LA LA LAND DROIDCON EDITION ▸ Cast ▸ Songs ▸

    Song details ▸ Mark song as favorite
  5. None
  6. !

  7. MODERN DEVELOPMENT OPTIONS

  8. MODERN DEVELOPMENT OPTIONS ▸ Native

  9. MODERN DEVELOPMENT OPTIONS ▸ Native ▸ Progressive Web App

  10. MODERN DEVELOPMENT OPTIONS ▸ Native ▸ Progressive Web App ▸

    React Native
  11. MODERN DEVELOPMENT OPTIONS ▸ Native ▸ Progressive Web App ▸

    React Native ▸ Flutter
  12. Our team decides to create a native iOS and Android

    app, but...
  13. ... we want to share our architecture between plaforms...

  14. ... and, if possible, we do not want to write

    the same business logic twice
  15. None
  16. WHY NOT?

  17. WHY NOT? ▸ Particular to our app/use case

  18. WHY NOT? ▸ Particular to our app/use case ▸ Time

  19. WHY NOT? ▸ Particular to our app/use case ▸ Time

    ▸ Money
  20. WHY NOT? ▸ Particular to our app/use case ▸ Time

    ▸ Money ▸ (Du)plicated efforts
  21. THAT SOUNDS AWESOME, BUT...

  22. CAN WE DO IT?

  23. !

  24. MULTIPLATFORM EFFORTS PRINCIPLES Isolate your business logic from: - Presentation

    - Operating system
  25. MULTIPLATFORM EFFORTS PRINCIPLES Isolate your business logic from: - Presentation

    - Operating system ▸ Also known as layers, clean code, the onion model...
  26. None
  27. Sooner than we think, multiplatform developers will be on very

    high demand — Jorge Coca, visionary
  28. WHAT ARE WE GOING TO ACHIEVE IN THIS TALK?

  29. WHAT ARE WE GOING TO ACHIEVE IN THIS TALK? ▸

    Intro to iOS development
  30. WHAT ARE WE GOING TO ACHIEVE IN THIS TALK? ▸

    Intro to iOS development ▸ Xcode
  31. WHAT ARE WE GOING TO ACHIEVE IN THIS TALK? ▸

    Intro to iOS development ▸ Xcode ▸ Swift
  32. WHAT ARE WE GOING TO ACHIEVE IN THIS TALK? ▸

    Intro to iOS development ▸ Xcode ▸ Swift ▸ UI design with Storyboards
  33. WHAT ARE WE GOING TO ACHIEVE IN THIS TALK? ▸

    Intro to iOS development ▸ Xcode ▸ Swift ▸ UI design with Storyboards ▸ ... from the point of view of an Android dev
  34. LET'S GET STARTED!

  35. LANGUAGES & TOOLS ANDROID IOS Android Studio Xcode Kotlin Swift

    Java Objective-C
  36. ANDROID IOS XML Layout Storyboard Activity/Fragment/ CustomView UIViewController BottomNavigationBar TabBarController

    RecyclerView with GridLayoutManager UICollectionView ImageView UIImageView TextView UILabel
  37. None
  38. THERE'S A TON OF DRAG & DROP IN STORYBOARDS

  39. UIVIEWCONTROLLER.SWIFT import UIKit class ViewController: UIViewController { override func viewDidLoad()

    { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
  40. UIVIEWCONTROLLER.SWIFT import UIKit class ViewController: UIViewController { override func viewDidLoad()

    { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
  41. UIVIEWCONTROLLER.SWIFT import UIKit class ViewController: UIViewController { override func viewDidLoad()

    { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
  42. UIVIEWCONTROLLER.SWIFT import UIKit class ViewController: UIViewController { override func viewDidLoad()

    { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
  43. UIVIEWCONTROLLER.SWIFT import UIKit class ViewController: UIViewController { override func viewDidLoad()

    { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
  44. EASY, RIGHT?

  45. UIVIEWCONTROLLER.SWIFT import UIKit class ViewController: UIViewController { override func viewDidLoad()

    { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } }
  46. UIKIT?

  47. UIKIT? ▸ Cocoa Touch is a framework for developing iOS

    applications.
  48. UIKIT? ▸ Cocoa Touch is a framework for developing iOS

    applications. ▸ UIKit for UI components (buttons, labels)
  49. UIKIT? ▸ Cocoa Touch is a framework for developing iOS

    applications. ▸ UIKit for UI components (buttons, labels) ▸ Foundation (NSString, NSObject) is the base framework for Apple products
  50. ... it's like the Android SDK and the Support Libraries

    and the language, all in one...
  51. LET'S ADD THE NAVIGATION CONTROLLER AND THE TAB BAR CONTROLLER

  52. None
  53. ... WAIT, DO WE HAVE CONTROLLERS AND VIEWCONTROLLERS? ▸ MVC

    is the default architectural pattern
  54. WHERE IS MY Adapter AND MY ViewHolder? protocol is the

    equivalent of an interface protocol extensions as Kotlin extensions
  55. WHERE IS MY Adapter AND MY ViewHolder? ▸ UICollectionView is

    like a RecyclerView, but for grids protocol is the equivalent of an interface protocol extensions as Kotlin extensions
  56. WHERE IS MY Adapter AND MY ViewHolder? ▸ UICollectionView is

    like a RecyclerView, but for grids ▸ UICollectionViewDataSource is the protocol that owns the data protocol is the equivalent of an interface protocol extensions as Kotlin extensions
  57. WHERE IS MY Adapter AND MY ViewHolder? ▸ UICollectionView is

    like a RecyclerView, but for grids ▸ UICollectionViewDataSource is the protocol that owns the data ▸ UICollectionViewDelegate is the protocol that owns the interaction protocol is the equivalent of an interface protocol extensions as Kotlin extensions
  58. WHERE IS MY Adapter AND MY ViewHolder? ▸ UICollectionView is

    like a RecyclerView, but for grids ▸ UICollectionViewDataSource is the protocol that owns the data ▸ UICollectionViewDelegate is the protocol that owns the interaction ▸ UICollectionViewCell is the UI representation of a single item protocol is the equivalent of an interface protocol extensions as Kotlin extensions
  59. ARTISTVIEWCELL.SWIFT import UIKit class ArtistViewCell : UICollectionViewCell { @IBOutlet var

    headshotImage: UIImageView! @IBOutlet var artistName: UILabel! func displayContent(headshot: UIImage, name: String) { headshotImage.image = headshot artistName.text = name } }
  60. ARTISTVIEWCELL.SWIFT import UIKit class ArtistViewCell : UICollectionViewCell { @IBOutlet var

    headshotImage: UIImageView! @IBOutlet var artistName: UILabel! func displayContent(headshot: UIImage, name: String) { headshotImage.image = headshot artistName.text = name } }
  61. @IBOUTLET It is a keyword added to a variable declaration.

    It binds your code variable to a UI element in the storyboard
  62. var AND let/val KOTLIN SWIFT MUTABLE? var var Yes val

    let No
  63. @IBACTION It is a keyword added to a method declaration.

    It binds your code method to a UI action in the storyboard
  64. IMPLICITLY UNWRAPPED OPTIONALS import UIKit class ArtistViewCell : UICollectionViewCell {

    @IBOutlet var headshotImage: UIImageView! @IBOutlet var artistName: UILabel! func displayContent(artist: Artist) { headshotImage.image = artist.headshot artistName.text = artist.name } }
  65. IMPLICITLY UNWRAPPED OPTIONALS import UIKit class ArtistViewCell : UICollectionViewCell {

    @IBOutlet var headshotImage: UIImageView! @IBOutlet var artistName: UILabel! func displayContent(artist: Artist) { headshotImage.image = artist.headshot artistName.text = artist.name } }
  66. ARTIST.SWIFT import Foundation import UIKit struct Artist { let name:

    String let headshot: UIImage init(name: String, headshot: UIImage) { self.name = name self.headshot = headshot } } let emmaStone = Artist(name: "Emma Stone", headshot: UIImage("emma.png"))
  67. ARTIST.SWIFT import Foundation import UIKit struct Artist { let name:

    String let headshot: UIImage init(name: String, headshot: UIImage) { self.name = name self.headshot = headshot } } let emmaStone = Artist(name: "Emma Stone", headshot: UIImage("emma.png"))
  68. ARTIST.SWIFT import Foundation import UIKit struct Artist { let name:

    String let headshot: UIImage init(name: String, headshot: UIImage) { self.name = name self.headshot = headshot } } let emmaStone = Artist(name: "Emma Stone", headshot: UIImage("emma.png"))
  69. ARTIST.SWIFT import Foundation import UIKit struct Artist { let name:

    String let headshot: UIImage init(name: String, headshot: UIImage) { self.name = name self.headshot = headshot } } let emmaStone = Artist(name: "Emma Stone", headshot: UIImage("emma.png"))
  70. STRUCTS

  71. STRUCTS ▸ Best intended for storing data properties

  72. STRUCTS ▸ Best intended for storing data properties ▸ ...

    are value type, meaning it's value is copied when assigned or passed
  73. CASTVIEWCONTROLLER import UIKit class CastViewController : UIViewController, UICollectionViewDataSource { @IBOutlet

    var castCollectionView: UICollectionView! let cast = CastStore.sharedInstance // MARK: UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cast.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "artistCellView", for: indexPath) as! ArtistViewCell let artist = cast[indexPath.row] cell.displayContent(headshot: artist.headshot, name: artist.name) return cell } }
  74. CASTVIEWCONTROLLER import UIKit class CastViewController : UIViewController, UICollectionViewDataSource { @IBOutlet

    var castCollectionView: UICollectionView! let cast = CastStore.sharedInstance // MARK: UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cast.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "artistCellView", for: indexPath) as! ArtistViewCell let artist = cast[indexPath.row] cell.displayContent(headshot: artist.headshot, name: artist.name) return cell } }
  75. CASTVIEWCONTROLLER import UIKit class CastViewController : UIViewController, UICollectionViewDataSource { @IBOutlet

    var castCollectionView: UICollectionView! let cast = CastStore.sharedInstance // MARK: UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cast.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "artistCellView", for: indexPath) as! ArtistViewCell let artist = cast[indexPath.row] cell.displayContent(headshot: artist.headshot, name: artist.name) return cell } }
  76. CASTVIEWCONTROLLER import UIKit class CastViewController : UIViewController, UICollectionViewDataSource { @IBOutlet

    var castCollectionView: UICollectionView! let cast = CastStore.sharedInstance // MARK: UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cast.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "artistCellView", for: indexPath) as! ArtistViewCell let artist = cast[indexPath.row] cell.displayContent(headshot: artist.headshot, name: artist.name) return cell } }
  77. CASTVIEWCONTROLLER import UIKit class CastViewController : UIViewController, UICollectionViewDataSource { @IBOutlet

    var castCollectionView: UICollectionView! let cast = CastStore.sharedInstance // MARK: UICollectionViewDataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cast.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "artistCellView", for: indexPath) as! ArtistViewCell let artist = cast[indexPath.row] cell.displayContent(headshot: artist.headshot, name: artist.name) return cell } }
  78. LET'S DO SOMETHING A BIT MORE COMPLEX

  79. SONGS

  80. SONGS ▸ UITableView with data source and delegate

  81. SONGS ▸ UITableView with data source and delegate ▸ UITableViewCell

    with 3 UILabel
  82. SONGS ▸ UITableView with data source and delegate ▸ UITableViewCell

    with 3 UILabel ▸ Segue to SongDetailsViewController
  83. SONG DETAILS

  84. SONG DETAILS ▸ Part of stack of UINavigationController

  85. SONG DETAILS ▸ Part of stack of UINavigationController ▸ UIImage

  86. SONG DETAILS ▸ Part of stack of UINavigationController ▸ UIImage

    ▸ UILabel
  87. SONG DETAILS ▸ Part of stack of UINavigationController ▸ UIImage

    ▸ UILabel ▸ UIText
  88. SONG DETAILS ▸ Part of stack of UINavigationController ▸ UIImage

    ▸ UILabel ▸ UIText ▸ UIButton
  89. None
  90. None
  91. EXAMPLE OF DATASTORE final class DataStore { static func songs()

    -> [Song] { var songs = [Song]() songs.append(Song(orderNumber: 1, name: "Another day of sun", artist: "La La Land Cast", isFavorite: true, lyrics: """ I think about that day I left him at a Greyhound station West of Santa Fé """)) return songs } }
  92. EXAMPLE OF DATASTORE final class DataStore { static func songs()

    -> [Song] { var songs = [Song]() songs.append(Song(orderNumber: 1, name: "Another day of sun", artist: "La La Land Cast", isFavorite: true, lyrics: """ I think about that day I left him at a Greyhound station West of Santa Fé """)) return songs } }
  93. EXAMPLE OF DATASTORE final class DataStore { static func songs()

    -> [Song] { var songs = [Song]() songs.append(Song(orderNumber: 1, name: "Another day of sun", artist: "La La Land Cast", isFavorite: true, lyrics: """ I think about that day I left him at a Greyhound station West of Santa Fé """)) return songs } }
  94. EXAMPLE OF DATASTORE final class DataStore { static func songs()

    -> [Song] { var songs = [Song]() songs.append(Song(orderNumber: 1, name: "Another day of sun", artist: "La La Land Cast", isFavorite: true, lyrics: """ I think about that day I left him at a Greyhound station West of Santa Fé """)) return songs } }
  95. import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate { @IBOutlet

    var tableView: UITableView! let songs = DataStore.songs() // MARK: UIViewController override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if (segue.identifier == "songDetailsSegue") { if let indexPath = self.tableView.indexPathForSelectedRow { let songDetailsViewController = segue.destination as! SongDetailsViewController let selectedSong = songs[indexPath.row] songDetailsViewController.selectedSong = selectedSong } } } // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } // MARK: UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "songDetailsSegue", sender: self) } }
  96. UITABLEVIEWDATASOURCE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate {

    let songs = DataStore.songs() ... // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } }
  97. UITABLEVIEWDATASOURCE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate {

    let songs = DataStore.songs() ... // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } }
  98. UITABLEVIEWDATASOURCE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate {

    let songs = DataStore.songs() ... // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } }
  99. UITABLEVIEWDATASOURCE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate {

    let songs = DataStore.songs() ... // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } }
  100. UITABLEVIEWDATASOURCE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate {

    let songs = DataStore.songs() ... // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } }
  101. UITABLEVIEWDATASOURCE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate {

    let songs = DataStore.songs() ... // MARK: UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return songs.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "songCellIdentifier") as! SongViewCell let song = songs[indexPath.row] cell.bind(song: song) return cell } }
  102. UITABLEVIEWDELEGATE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet var tableView: UITableView! let songs = DataStore.songs() // MARK: UIViewController override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if (segue.identifier == "songDetailsSegue") { if let indexPath = self.tableView.indexPathForSelectedRow { let songDetailsViewController = segue.destination as! SongDetailsViewController let selectedSong = songs[indexPath.row] songDetailsViewController.selectedSong = selectedSong } } } // MARK: UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "songDetailsSegue", sender: self) } ... }
  103. UITABLEVIEWDELEGATE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet var tableView: UITableView! let songs = DataStore.songs() // MARK: UIViewController override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if (segue.identifier == "songDetailsSegue") { if let indexPath = self.tableView.indexPathForSelectedRow { let songDetailsViewController = segue.destination as! SongDetailsViewController let selectedSong = songs[indexPath.row] songDetailsViewController.selectedSong = selectedSong } } } // MARK: UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "songDetailsSegue", sender: self) } ... }
  104. UITABLEVIEWDELEGATE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet var tableView: UITableView! let songs = DataStore.songs() // MARK: UIViewController override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if (segue.identifier == "songDetailsSegue") { if let indexPath = self.tableView.indexPathForSelectedRow { let songDetailsViewController = segue.destination as! SongDetailsViewController let selectedSong = songs[indexPath.row] songDetailsViewController.selectedSong = selectedSong } } } // MARK: UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "songDetailsSegue", sender: self) } ... }
  105. UITABLEVIEWDELEGATE import UIKit class SongsViewController : UIViewController, UITableViewDataSource, UITableViewDelegate {

    @IBOutlet var tableView: UITableView! let songs = DataStore.songs() // MARK: UIViewController override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if (segue.identifier == "songDetailsSegue") { if let indexPath = self.tableView.indexPathForSelectedRow { let songDetailsViewController = segue.destination as! SongDetailsViewController let selectedSong = songs[indexPath.row] songDetailsViewController.selectedSong = selectedSong } } } // MARK: UITableViewDelegate func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { performSegue(withIdentifier: "songDetailsSegue", sender: self) } ... }
  106. SONGDETAILSVIEWCONTROLLER import UIKit class SongDetailsViewController : UIViewController { var selectedSong:

    Song! @IBOutlet var artistLabel: UILabel! @IBOutlet var descriptionText: UITextView! @IBOutlet weak var favoriteButton: UIButton! override func viewDidLoad() { super.viewDidLoad() artistLabel.text = selectedSong.artist descriptionText.text = selectedSong.lyrics } @IBAction func toggleFavorite(_ sender: UIButton) { if (selectedSong.isFavorite) { selectedSong.isFavorite = false favoriteButton.setTitle(" ! ", for: UIControlState.normal) } else { selectedSong.isFavorite = true favoriteButton.setTitle(" ❤ ", for: UIControlState.normal) } } }
  107. SONGDETAILSVIEWCONTROLLER import UIKit class SongDetailsViewController : UIViewController { var selectedSong:

    Song! @IBOutlet var artistLabel: UILabel! @IBOutlet var descriptionText: UITextView! @IBOutlet weak var favoriteButton: UIButton! override func viewDidLoad() { super.viewDidLoad() artistLabel.text = selectedSong.artist descriptionText.text = selectedSong.lyrics } @IBAction func toggleFavorite(_ sender: UIButton) { if (selectedSong.isFavorite) { selectedSong.isFavorite = false favoriteButton.setTitle(" ! ", for: UIControlState.normal) } else { selectedSong.isFavorite = true favoriteButton.setTitle(" ❤ ", for: UIControlState.normal) } } }
  108. SONGDETAILSVIEWCONTROLLER import UIKit class SongDetailsViewController : UIViewController { var selectedSong:

    Song! @IBOutlet var artistLabel: UILabel! @IBOutlet var descriptionText: UITextView! @IBOutlet weak var favoriteButton: UIButton! override func viewDidLoad() { super.viewDidLoad() artistLabel.text = selectedSong.artist descriptionText.text = selectedSong.lyrics } @IBAction func toggleFavorite(_ sender: UIButton) { if (selectedSong.isFavorite) { selectedSong.isFavorite = false favoriteButton.setTitle(" ! ", for: UIControlState.normal) } else { selectedSong.isFavorite = true favoriteButton.setTitle(" ❤ ", for: UIControlState.normal) } } }
  109. SONGDETAILSVIEWCONTROLLER import UIKit class SongDetailsViewController : UIViewController { var selectedSong:

    Song! @IBOutlet var artistLabel: UILabel! @IBOutlet var descriptionText: UITextView! @IBOutlet weak var favoriteButton: UIButton! override func viewDidLoad() { super.viewDidLoad() artistLabel.text = selectedSong.artist descriptionText.text = selectedSong.lyrics } @IBAction func toggleFavorite(_ sender: UIButton) { if (selectedSong.isFavorite) { selectedSong.isFavorite = false favoriteButton.setTitle(" ! ", for: UIControlState.normal) } else { selectedSong.isFavorite = true favoriteButton.setTitle(" ❤ ", for: UIControlState.normal) } } }
  110. SONGDETAILSVIEWCONTROLLER import UIKit class SongDetailsViewController : UIViewController { var selectedSong:

    Song! @IBOutlet var artistLabel: UILabel! @IBOutlet var descriptionText: UITextView! @IBOutlet weak var favoriteButton: UIButton! override func viewDidLoad() { super.viewDidLoad() artistLabel.text = selectedSong.artist descriptionText.text = selectedSong.lyrics } @IBAction func toggleFavorite(_ sender: UIButton) { if (selectedSong.isFavorite) { selectedSong.isFavorite = false favoriteButton.setTitle(" ! ", for: UIControlState.normal) } else { selectedSong.isFavorite = true favoriteButton.setTitle(" ❤ ", for: UIControlState.normal) } } }
  111. PROFIT TIME! !"

  112. ... well, not really, right?

  113. !

  114. Let's talk about networking and persistence

  115. NETWORKING

  116. NETWORKING ▸ URLRequest as the simplest method. Built in.

  117. NETWORKING ▸ URLRequest as the simplest method. Built in. ▸

    URLSession seems to be the current preferred option. Built in.
  118. NETWORKING ▸ URLRequest as the simplest method. Built in. ▸

    URLSession seems to be the current preferred option. Built in. ▸ Alamofire is an easy to use, but heavy, framework
  119. NETWORKING ▸ URLRequest as the simplest method. Built in. ▸

    URLSession seems to be the current preferred option. Built in. ▸ Alamofire is an easy to use, but heavy, framework ▸ ... but hey, nothing like Retrofit
  120. NETWORKING ▸ URLRequest as the simplest method. Built in. ▸

    URLSession seems to be the current preferred option. Built in. ▸ Alamofire is an easy to use, but heavy, framework ▸ ... but hey, nothing like Retrofit ▸ ... and let's not even talk about Moshi or Gson...
  121. CALLING A REST API let url = URL(string: "https://la.la.land/songs/top?api_key=DEMO_KEY") let

    task = URLSession.shared.dataTask(with: url!) { (data, response, error) in if let data = data { do { let jsonSerialized = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] if let json = jsonSerialized, let name = json["name"], let artist = json["artist"] { print(url) print(explanation) } } catch let error as NSError { print(error.localizedDescription) } } else if let error = error { print(error.localizedDescription) } } task.resume()
  122. CALLING A REST API let url = URL(string: "https://la.la.land/songs/top?api_key=DEMO_KEY") let

    task = URLSession.shared.dataTask(with: url!) { (data, response, error) in if let data = data { do { let jsonSerialized = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] if let json = jsonSerialized, let name = json["name"], let artist = json["artist"] { print(url) print(explanation) } } catch let error as NSError { print(error.localizedDescription) } } else if let error = error { print(error.localizedDescription) } } task.resume()
  123. CALLING A REST API let url = URL(string: "https://la.la.land/songs/top?api_key=DEMO_KEY") let

    task = URLSession.shared.dataTask(with: url!) { (data, response, error) in if let data = data { do { let jsonSerialized = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] if let json = jsonSerialized, let name = json["name"], let artist = json["artist"] { print(url) print(explanation) } } catch let error as NSError { print(error.localizedDescription) } } else if let error = error { print(error.localizedDescription) } } task.resume()
  124. CALLING A REST API let url = URL(string: "https://la.la.land/songs/top?api_key=DEMO_KEY") let

    task = URLSession.shared.dataTask(with: url!) { (data, response, error) in if let data = data { do { let jsonSerialized = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any] if let json = jsonSerialized, let name = json["name"], let artist = json["artist"] { print(url) print(explanation) } } catch let error as NSError { print(error.localizedDescription) } } else if let error = error { print(error.localizedDescription) } } task.resume()
  125. PERSISTENCE

  126. PERSISTENCE ▸ SQLite supported out of the box

  127. PERSISTENCE ▸ SQLite supported out of the box ▸ FMDB

    or SQLite.swift as wrappers
  128. PERSISTENCE ▸ SQLite supported out of the box ▸ FMDB

    or SQLite.swift as wrappers ▸ ... but not as good as Room
  129. PERSISTENCE ▸ SQLite supported out of the box ▸ FMDB

    or SQLite.swift as wrappers ▸ ... but not as good as Room ▸ Oh yeah! And Core Data
  130. CORE DATA

  131. CORE DATA ▸ Manage object graphs and object lifecycle, including

    persistence.
  132. CORE DATA ▸ Manage object graphs and object lifecycle, including

    persistence. ▸ Use Core Data to manage the model layer objects in your application.
  133. CORE DATA ▸ Manage object graphs and object lifecycle, including

    persistence. ▸ Use Core Data to manage the model layer objects in your application. ▸ ... so it's like a model and persistence layer, all in one
  134. !"

  135. None
  136. Biggest efforts on Multiplatform happening on the networking and persistence

    layers
  137. LEARNING RESOURCES

  138. LEARNING RESOURCES ▸ Ray Wenderlich, community supported

  139. LEARNING RESOURCES ▸ Ray Wenderlich, community supported ▸ Big Nerd

    Ranch, world class training!
  140. LEARNING RESOURCES ▸ Ray Wenderlich, community supported ▸ Big Nerd

    Ranch, world class training! ▸ NSHipster by @mattt
  141. LEARNING RESOURCES ▸ Ray Wenderlich, community supported ▸ Big Nerd

    Ranch, world class training! ▸ NSHipster by @mattt ▸ Flight School, also by @mattt
  142. ...and finally...

  143. None
  144. Thank you!