Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building Kotlin Muiltiplatform Apps for Android & iOS

Building Kotlin Muiltiplatform Apps for Android & iOS

This presentation is a walk through on how to build a multiplatform app for Android & iOS. We look at how to structure and setup a project for Android and iOS. Then, we look how to a build an Android & iOS app with this structure. The sample app is built using Model View Presenter. We explore how to share our models and presenters in a multiplatform project. We also look at the limitations that you will come across along the way.

Mohit S

May 23, 2018
Tweet

More Decks by Mohit S

Other Decks in Programming

Transcript

  1. Kotlin Multiplatform for Android & iOS • Why Kotlin Multiplatform?

    • Project Structure • Setting up project for Android & iOS • Sharing Presenters and Models in MVP • Objective-C/Swift interop with Kotlin
  2. • Why Kotlin Multiplatform? • Project Structure • Setting up

    project for Android & iOS • Sharing Presenters and Models in MVP • Objective-C/Swift interop with Kotlin Kotlin Multiplatform for Android & iOS
  3. Duplicate Business Logic • API Requests • Data Transfer Objects

    • Tracking Analytics • Title and Description Validation Logic
  4. Cross platform frameworks Benefits • Good for building new apps

    • Write once for both Android and iOS Drawbacks • No interop with existing languages and frameworks • Hard to convert large native apps
  5. • Kotlin Multiplatform Structure • Why Kotlin Multiplatform? • Kotlin

    Multiplatform Structure • Setting up project for Android & iOS • Sharing Presenters and Models in MVP • Objective-C/Swift interop with Kotlin Kotlin Multiplatform for Android & iOS
  6. Multiplatform Structure 3 types of modules • Common Module •

    Platform Module (JVM & iOS) • Regular Module (Android & iOS app)
  7. Common Module • Kotlin only code • Platform independent declarations

    using expect keyword • Kotlin Standard Common Library (kotlin-stdlib-common)
  8. Platform Independent Declarations expect class Platform() { val platform: String

    } class Greeting { fun greeting(): String = "Hello, ${Platform().platform}" }
  9. Platform Module • Kotlin only code • Platform implementations using

    actual keyword • Interop with Objective-C libraries (iOS)
  10. Platform Specific Declaration (JVM) actual class Platform actual constructor() {

    actual val platform: String = “Android" } expect class Platform() { val platform: String }
  11. Platform Specific Declaration (iOS) actual class Platform actual constructor() {

    actual val platform: String = “iOS” } expect class Platform() { val platform: String }
  12. Compile Android App class Greeting { fun greeting(): String =

    “Hello, ${Platform().platform}" } public final class Greeting public constructor() { public final fun greeting(): kotlin.String { !/* compiled code !*/ } } android.jar
  13. Compile iOS App Common 
 module iOS 
 module Kotlin

    Native
 Compiler Framework Objective-C
  14. Compile iOS App Common 
 module iOS 
 module Kotlin

    Native
 Compiler Framework Objective-C
  15. Compile iOS App class Greeting { fun greeting(): String =

    “Hello, ${Platform().platform}" }
  16. • Setting up project for Android & iOS • Why

    Kotlin Multiplatform? • Project Structure • Setting up project for Android & iOS • Sharing Presenters and Models in MVP • Objective-C/Swift interop with Kotlin Kotlin Multiplatform for Android & iOS
  17. Setting up Kotlin multiplatform project 1. Common Module
 2. JVM

    Platform Module
 3. iOS Platform Module
 4. Android App
 5. iOS App
  18. Common Module apply plugin: 'kotlin-platform-common' group = 'com.learn.shared' version =

    1.0 dependencies { !// Set up compilation dependency on common Kotlin stdlib compile “org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version" }
  19. JVM Platform Module apply plugin: 'kotlin-platform-jvm' group = 'com.learn.shared' version

    = 1.0 dependencies { !// Specify Kotlin/JVM stdlib dependency. implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" !// Specify dependency on a common project for Kotlin multiplatform build. expectedBy project(':shared:common') }
  20. iOS Platform Module apply plugin: 'konan' !// Specify targets to

    build the framework: iOS and iOS simulator konan.targets = ['ios_arm64', 'ios_x64'] konanArtifacts { !// Declare building into a framework. framework('Shared') { !// The multiplatform support is disabled by default. enableMultiplatform true } } dependencies { !// Specify dependency on a common project for Kotlin multiplatform build expectedBy project(':shared:common') }
  21. iOS Platform Module apply plugin: 'konan' !// Specify targets to

    build the framework: iOS and iOS simulator konan.targets = ['ios_arm64', 'ios_x64'] konanArtifacts { !// Declare building into a framework. framework('Shared') { !// The multiplatform support is disabled by default. enableMultiplatform true } } dependencies { !// Specify dependency on a common project for Kotlin multiplatform build expectedBy project(':shared:common') } apply plugin: 'konan'
  22. iOS Platform Module apply plugin: 'konan' !// Specify targets to

    build the framework: iOS and iOS simulator konan.targets = ['ios_arm64', 'ios_x64'] konanArtifacts { !// Declare building into a framework. framework('Shared') { !// The multiplatform support is disabled by default. enableMultiplatform true } } dependencies { !// Specify dependency on a common project for Kotlin multiplatform build expectedBy project(':shared:common') } !// Specify targets to build the framework: iOS and iOS simulator konan.targets = ['ios_arm64', 'ios_x64']
  23. Konan Targets • android_arm32 • android_arm64 • ios_x64 • linux_x64

    • mingw_x64 • macos_x64 • linux_arm32_hfp • linux_mips32 • linux_mipsel32 • wasm32
  24. konanArtifacts { !// Declare building into a framework. framework('Shared') {

    !// The multiplatform support is disabled by default. enableMultiplatform true } } iOS Platform Module apply plugin: 'konan' !// Specify targets to build the framework: iOS and iOS simulator konan.targets = ['ios_arm64', 'ios_x64'] konanArtifacts { !// Declare building into a framework. framework('Shared') { !// The multiplatform support is disabled by default. enableMultiplatform true } } dependencies { !// Specify dependency on a common project for Kotlin multiplatform build expectedBy project(':shared:common') }
  25. dependencies { !// Specify dependency on a common project for

    Kotlin multiplatform build expectedBy project(':shared:common') } iOS Platform Module apply plugin: 'konan' !// Specify targets to build the framework: iOS and iOS simulator konan.targets = ['ios_arm64', 'ios_x64'] konanArtifacts { !// Declare building into a framework. framework('Shared') { !// The multiplatform support is disabled by default. enableMultiplatform true } } dependencies { !// Specify dependency on a common project for Kotlin multiplatform build expectedBy project(':shared:common') }
  26. apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android

    { … } dependencies { … implementation ‘com.learn.shared:jvm:1.0' … } Android App
  27. • Sharing Presenters and Models in MVP Kotlin Multiplatform for

    Android & iOS • Why Kotlin Multiplatform? • Project Structure • Setting up project for Android & iOS • Sharing Presenters and Models in MVP • Objective-C/Swift interop with Kotlin
  28. Multiplatform App in MVP • Get videos from API •

    Track video selection to Localytics • Play the video
  29. API GET /videos { "videos": [ { "id": 1, 


    “title": "Big Buck Bunny", "desc": "The Big Buck Bunny video.", "url": “https:!//clips.mohit.com/big_buck_bunny.mp4” }, { "id": 2, "title": "Timelapse Video of Sky", "desc": "Awesome timelapse video.", "url": “https:!//clips.mohit.com/timelapse_video_of_sky.mp4” }
 ] }
  30. Alternatives 1. Build common HTTP client for JVM and Native


    2. Use Dependency Injection to inject API requests
 3. Use NSURLConnection in iOS and Retrofit in Android
  31. JVM & iOS Platform Modules runSuspend { HttpClient().request { with(url)

    { protocol = URLProtocol.HTTPS host = endpoint port = 443 } } }.then { response !-> println(response.body) client.close() }
  32. iOS App class VideosInteractorImpl : NSObject, SharedVideosInteractor { func fetchVideos(callback:

    SharedFetchDataCallback) { Alamofire.request(uri).responseJSON { response in if let json = response.result.value { let videos = self.parseResponse(response: json) callback.onFetchedData(data: videos) } else { callback.onError() } } }
  33. Android App class VideosInteractorImpl : VideosInteractor { override fun fetchVideos(fetchedCallback:

    FetchDataCallback<List<Video!>>) { RetrofitFactory.create().videos().enqueue(object: Callback<List<Video!>> { override fun onResponse(call: Call<List<Video!>>?, response: Response<List<Video!>> val videos = response!?.body() if (videos !!= null) { fetchedCallback.onFetchedData(videos) } else { fetchedCallback.onError() } } override fun onFailure(call: Call<List<Video!>>?, t: Throwable?) { fetchedCallback.onError() } }) }
  34. Alternative Approaches • Use example-gen branch from Kotlin Serialization library

    • Create a Codable protocol extension for you model on iOS • Manually decode JSON on iOS
  35. MVP Contract (Common Module) class FeedContract { interface View {

    fun showVideos(videos: List<Video>) fun showErrorMessage(message: String) fun showVideoDetails(video : Video) } … }
  36. MVP Contract (Common Module) class FeedContract { … interface Presenter

    : BasePresenter<View> { fun onSelectedVideo(video: Video) } }
  37. Presenter (Common Module) class FeedPresenter( private val videosInteractor: VideosInteractor ):

    FeedContract.Presenter {
 override fun onViewAttached(view: FeedContract.View) { this.view = view videosInteractor.fetchVideos(object: FetchDataCallback<List<Video!>> { override fun onFetchedData(data: List<Video>) { view.showVideos(data) } override fun onError() { view.showErrorMessage("Failed to load feed.") } }) }
  38. iOS Generated Classes in Objective-C • Kotlin Base • FeedContract

    is a protocol • VideosInteractor is a protocol • Feed Presenter
  39. ViewController iOS App class FeedViewController: UITableViewController, SharedFeedContractView { override func

    viewDidLoad() { super.viewDidLoad() let videosInteractor = VideosInteractorImpl() feedPresenter = SharedFeedPresenter(videosInteractor: videosInteractor) feedPresenter!?.onViewAttached(view: self) } func showVideos(videos: [SharedVideo]) { self.videos = videos tableView.reloadData() }

  40. • Why Kotlin Multiplatform? • Project Structure • Setting up

    project for Android & iOS • Sharing Presenters and Models in MVP • • Objective-C/Swift interop with Kotlin Kotlin Multiplatform for Android & iOS
  41. Kotlin Bindings for AVKit @kotlinx.cinterop.ExternalObjCClass 
 public open class AVPlayer

    : platform.darwin.NSObject { 
 public companion object : platform.AVFoundation.AVPlayerMeta, 
 kotlinx.cinterop.ObjCClassOf<platform.AVFoundation.AVPlayer> { } @kotlinx.cinterop.ObjCConstructor public constructor(
 uRL: platform.Foundation.NSURL) { !/* compiled code !*/ } 
 …
  42. Playing video on iOS with Kotlin fun playVideo(view: UIView) {


    val videoURL = NSURL(string = 
 “https:!//clips.mohit.com/big_buck_bunny.mp4”) val player = AVPlayer(videoURL) val playerLayer = AVPlayerLayer() playerLayer.player = player playerLayer.frame = view.bounds view.layer.addSublayer(playerLayer) player.play() }
  43. Playing video on iOS with Kotlin class ViewController: UIViewController {

    override func viewDidLoad() { super.viewDidLoad() Shared().playVideo(view: self.view) } … }
  44. Future • One IDE • Official common HTTP and Serialization

    Library • Support for pure Swift modules