$30 off During Our Annual Pro Sale. View Details »

Scala.jsとAndroidでドメイン層を共有しよう / Scala.js and Android

AGAWA Koji
December 12, 2019

Scala.jsとAndroidでドメイン層を共有しよう / Scala.js and Android

社内カンファレンスで発表した資料です。

AGAWA Koji

December 12, 2019
Tweet

More Decks by AGAWA Koji

Other Decks in Programming

Transcript

  1. Scala.jsͰAndroidͱυϝΠ
    ϯ૚Λڞ༗͠Α͏
    Ѩ઒ ߞ࢘ @atty303
    AI tech studio / CyberAgent, Inc.
    © 2019 CyberAgent, Inc.
    1/35

    View Slide

  2. Ѩ઒ ߞ࢘
    ςοΫϦʔυ
    @ൢଅαΠωʔδAd
    • ۀ຿ྺ 14 ೥ / Scala ྺ 5 ೥
    • ήʔϜ޷͖ (Steam ϝΠϯ)
    ࠷ۙ͸ࣨ಺αΠΫϦϯάΛؤ
    ுͬͯ·͢ɻ
    © 2019 CyberAgent, Inc. 2/35

    View Slide

  3. ൢଅαΠωʔδAd
    © 2019 CyberAgent, Inc. 3/35

    View Slide

  4. © 2019 CyberAgent, Inc. 4/35

    View Slide

  5. ৽͍͠ސ٬ମݧͱ࣮ళ
    ฮͷՁ஋Λ૑଄͢Δ
    Λϛογϣϯʹܝ͛ɺίϯϏχ
    ΤϯεετΞɾυϥοάετ
    Ξɾεʔύʔͱ͍࣮ͬͨళฮͷ
    σδλϧԽɾϝσΟΞԽΛɺσ
    δλϧαΠωʔδͱίϯςϯπ
    ʗ޿ࠂ഑৴γεςϜΛ༻͍࣮ͯ
    ݱ͠·͢ɻ
    © 2019 CyberAgent, Inc. 5/35

    View Slide

  6. αΠωʔδͷٕज़ελοΫ
    • Android 6.0 (API 23)
    • Kotlin
    • MediaPlayer + TextureView
    • WebView + Scala.js
    © 2019 CyberAgent, Inc. 6/35

    View Slide

  7. ϨΠϠʔͱٕज़
    • Kotlin ͸υϥΠό૚ͷΈ
    • υϝΠϯ͓ΑͼΞϓϦέʔ
    γϣϯ૚͸ Scala.js
    © 2019 CyberAgent, Inc. 7/35

    View Slide

  8. Kotlin/Scalaͷίʔυྔ
    Kotlin
    $ wc -l `find context/signage/ -name '*.kt'`
    1815 total
    Scala
    $ wc -l `find context/signage/ -name '*.scala'`
    6515 total
    © 2019 CyberAgent, Inc. 8/35

    View Slide

  9. Scala.js
    © 2019 CyberAgent, Inc. 9/35

    View Slide

  10. Scala.js
    • Scala ͔Β JavaScript ʹίϯύΠϧ͢Δ΍ͭ
    • 2013೥͔Β։ൃ͕ଓ͍͍ͯΔ
    • ࠷ۙ 1.0-RC ͕ग़ͯɺ2020೥1݄ʹ 1.0 ϦϦʔε༧

    © 2019 CyberAgent, Inc. 10/35

    View Slide

  11. ݸਓతͳҹ৅
    • ScalaͰJSॻ͚Δͷ͸໘ന͍
    • JSΤίγεςϜͱͷ૬ੑΛߟ͑ͨΒTypeScript͕
    ແ೉Ͱ͸ʁ
    • มԽͷܹ͍͠ React, Vue, ... ͳͲΛ࢖͑Δͷ͔
    • ࢖͍ॴ͕೉ͦ͠͏
    © 2019 CyberAgent, Inc. 11/35

    View Slide

  12. ࠓճ࠾༻ͨ͠ཧ༝
    • υϝΠϯ૚ΛαʔόʔͱαΠωʔδͰڞ༗ͨ͠
    ͔ͬͨ
    • αʔόʔ͸ Scala Ͱ֬ఆɺαΠωʔδ͸ Android
    Ͱ֬ఆɺͱ͍͏ঢ়گ
    • Android ΑΓ(׳Ε͍ͯΔ) Web ٕज़ʹد͔ͤͨͬ
    ͨ
    → Scala.js ͕ϕετϚονͰ͸ʂʁ
    © 2019 CyberAgent, Inc. 12/35

    View Slide

  13. Scala.js ͷಋೖ
    • project/plugins.sbt ʹ Scala.js ϓϥάΠϯΛ௥Ճ
    addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.31")
    // JVM/JS ΫϩεϏϧυ͕ඞཁͳ৔߹͸Լه΋௥Ճ
    addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.6.1")
    © 2019 CyberAgent, Inc. 13/35

    View Slide

  14. Scala.js ͷಋೖ
    • ϓϩδΣΫτઃఆͰ
    enablePlugins(ScalaJSPlugin) Λ௥Ճ͢Δ
    lazy val signageDriverSignageJs = (project in file("context/signage/driver/signage-js"))
    .dependsOn(signageApplication.js)
    .enablePlugins(ScalaJSPlugin)
    .settings(...)
    © 2019 CyberAgent, Inc. 14/35

    View Slide

  15. Scala.js ͷಋೖ

    sbt Ͱ fastOptJS λεΫΛ࣮ߦ͢Δͱ js ͕ॻ͖ग़
    ͞ΕΔ
    $ sbt fastOptJS
    Ҏ্ɺ؆୯Ͱ͢Ͷ
    © 2019 CyberAgent, Inc. 15/35

    View Slide

  16. Scala.jsಋೖ·Ͱ
    • औΓֻ͔͔ͬͯΒ 2 ೔ఔ౓Ͱ׬ྃ
    • ΋ͱ΋ͱ Scala.js ͷ͜ͱΛҰ੾ߟྀ͍ͯ͠ͳ͍
    ίʔυϕʔε
    • υϝΠϯ૚͸ϥΠϒϥϦґଘ͕গͳ͔ͬͨ
    © 2019 CyberAgent, Inc. 16/35

    View Slide

  17. AndroidͱScala.jsͷܨ͗
    © 2019 CyberAgent, Inc. 17/35

    View Slide

  18. interop
    KotlinͷυϥΠό૚ͱScala.jsͷΞϓϦέʔγϣϯ૚
    Λܨ͙ύʔπ
    © 2019 CyberAgent, Inc. 18/35

    View Slide

  19. Scala.js ଆ
    import scala.scalajs.js
    import scala.scalajs.js.annotation.JSGlobal
    @js.native
    @JSGlobal
    object interop extends js.Object {
    def requestCacheContents(urls: js.Array[String]): Unit = js.native
    def publish(
    dataBase64: String,
    method: String,
    endpoint: String,
    headers: String
    ): Unit = js.native
    def loadSlot(
    index: Int,
    src: String,
    poster: String,
    isImage: Boolean,
    invertedSlotRotation: Int
    ): Unit = js.native
    def flipSlot(index: Int): Unit = js.native
    }
    © 2019 CyberAgent, Inc. 19/35

    View Slide

  20. Androidଆ
    import android.webkit.JavascriptInterface
    import android.webkit.WebView
    class WebViewInterop(
    val requestCacheContentsDelegate: RequestCacheContentsDelegate
    // publish ...
    ) {
    fun addToWebView(webView: WebView): WebView {
    webView.addJavascriptInterface(this, "interop")
    return webView
    }
    @JavascriptInterface
    fun requestCacheContents(urls: Array): Unit {
    requestCacheContentsDelegate.apply(urls)
    }
    // publish ...
    }
    © 2019 CyberAgent, Inc. 20/35

    View Slide

  21. υϝΠϯ૚ͷશମ૾
    © 2019 CyberAgent, Inc. 21/35

    View Slide

  22. SignageProgram
    ͜ΕΛαʔόʔͰ૊ΈཱͯɺAndroidଆͰར༻͢Δͷ
    ͕ओ໨త
    • Protocol Buffers Ͱαʔόʔ͔ΒAndroid΁ૹ৴
    • Scala ͰγϦΞϥΠζͯ͠ɺScala.js ͰσγϦΞϥ
    Πζ
    • ྆ଆͱ΋ ScalaPB Ͱ؆୯ʹͰ͖ͨ
    © 2019 CyberAgent, Inc. 22/35

    View Slide

  23. Scala.js ར༻ͷ޻෉
    © 2019 CyberAgent, Inc. 23/35

    View Slide

  24. Long ܕͷར༻ʹؾΛ͚ͭΔ
    • JVM ͷ Long ͸ 63bit
    • JS ͷ Number ͸ Double ૬౰ͳͷͰԾ਺෦ 52bit

    LocalTime#toNanoOfDay() Ͱਫ਼౓མͪʹૺ۰
    • ࣮ߦ࣌ΤϥʔʹͳΔ

    toSecondOfDay()
    +
    getNano() ͰରԠ
    © 2019 CyberAgent, Inc. 24/35

    View Slide

  25. Java API ͸جૅతͳ෦෼ͷΈར༻Մ

    • ৄࡉ͸ https://github.com/scala-js/scala-js/tree/
    master/javalib/src/main/scala/java Λࢀর
    © 2019 CyberAgent, Inc. 25/35

    View Slide

  26. Java API Ͱࠓճࠔͬͨͱ͜Ζ
    • java.time ύοέʔδ͕σϑΥϧτͰ͸࢖͑ͳ͍
    • "com.zoepepper" %%% "scalajs-jsjoda-as-java-
    time" ͷར༻Ͱղܾ
    © 2019 CyberAgent, Inc. 26/35

    View Slide

  27. Scala.js ͱ࢖͍ͬͯΔϥΠϒϥϦ
    • ScalaPB
    • "org.typelevel" %%% "cats-core"
    • "org.typelevel" %%% "cats-effect"
    • "com.beachape" %%% "enumeratum"
    • "io.estatico" %%% "newtype"
    • "eu.timepit" %%% "refined"
    • "io.lemonlabs" %%% "scala-uri"
    © 2019 CyberAgent, Inc. 27/35

    View Slide

  28. UseCase Λ JVM/JS Ͱڞ༗͢Δ
    • DisplayContentUseCase ͸ JVM/JS Ͱڞ༗͍ͨ͠
    • JS Ͱ͸αΠωʔδʹίϯςϯπΛදࣔ͢Δ
    • JVM Ͱ͸දࣔ͞ΕΔίϯςϯπΛϓϨϏϡʔ͢
    Δ
    • UseCase ͕ڞ௨ͳͷͰ LocationRepository ͳͲ
    ͷ Repository ΋ JVM ͱ JS ͰಉҰ trait Λ࢖͍ͨ
    © 2019 CyberAgent, Inc. 28/35

    View Slide

  29. ࠷ॳͷܗ
    trait LocationRepository {
    def findById(id: LocationId): ConnectionIO[Option[Location]]
    }

    doobie.free.connection.ConnectionIO
    • doobie (JDBC ϥΠϒϥϦ) ͷ JS ൛͸ଘࡏ͠ͳ
    ͍(౰વ)
    • JS ͔Β ConnectionIO ܕΛར༻Ͱ͖ͳ͍
    • ͳΜΒ͔ͷந৅Խ͕ඞཁ
    © 2019 CyberAgent, Inc. 29/35

    View Slide

  30. Repositoryͷந৅Խͨ͠ܗ
    trait LocationRepository[F[_]] {
    def findById(id: LocationId): F[Option[Location]]
    }
    // JVM
    trait PostgresLocationRepository extends LocationRepository[ConnectionIO] {
    def findById(id: LocationId): ConnectionIO[Option[Location]]
    }
    // JS
    trait InMemoryLocationRepository extends LocationRepository[IO]
    {
    def findById(id: LocationId): IO[Option[Location]]
    }
    © 2019 CyberAgent, Inc. 30/35

    View Slide

  31. Transactor΋ந৅Խ
    trait Transactor[M[_], N[_]] {
    def trans(implicit ev: Bracket[N, Throwable]): M ~> N
    }
    // Doobie
    class DoobieTransactor[N[_]](
    underlying: doobie.util.transactor.Transactor[N]
    ) extends Transactor[ConnectionIO, N] {
    override def trans(implicit ev: Bracket[N, Throwable]): ConnectionIO ~> N =
    underlying.trans
    }
    // JS
    class RawTransactor[N[_]](
    implicit liftIO: LiftIO[N]
    ) extends Transactor[IO, N] {
    override def trans(implicit ev: Bracket[N, Throwable]): IO ~> N =
    LiftIO.liftK[N]
    }
    © 2019 CyberAgent, Inc. 31/35

    View Slide

  32. རศੑͷͨΊʹ Transactable ܕΫϥ
    εΛఆٛ
    trait Transactable[M[_]] {
    def transact[A](transactor: Transactor[M, IO])(m: M[A])(implicit ev: Bracket[IO, Throwable]): IO[A]
    }
    // Doobie
    implicit lazy val transactableForConnectionIO: Transactable[ConnectionIO] = new Transactable[ConnectionIO] {
    override def transact[A](
    transactor: repository.Transactor[ConnectionIO, IO]
    )(m: ConnectionIO[A])(implicit ev: Bracket[IO, Throwable]): IO[A] =
    transactor.trans.apply(m)
    }
    // JS
    implicit lazy val transactable: Transactable[IO] = new Transactable[IO] {
    override def transact[A](transactor: Transactor[IO, IO])(m: IO[A])(implicit ev: Bracket[IO, Throwable]): IO[A] =
    transactor.trans.apply(m)
    }
    © 2019 CyberAgent, Inc. 32/35

    View Slide

  33. UseCase͔Βར༻͢Δ
    class DisplayContentUseCase[F[_]](
    transactor: Resource[IO, Transactor[F, IO]],
    locationRepository: LocationRepository[F]
    )(
    implicit bracketForF: Bracket[F, Throwable],
    transactableForF: Transactable[F]
    ) {
    def apply(locationId: LocationId): IO[Unit] = {
    val transIo: IO[Location] = transactor.use { xa =>
    val io = for {
    maybeLocation <- locationRepository.findById(locationId)
    location <- maybeLocation.liftTo[F](new LocationNotFountError)
    } yield location
    Transactable[F].transact(xa)(io)
    }
    }
    for {
    location <- transIo
    // ...
    } yield ()
    }
    © 2019 CyberAgent, Inc. 33/35

    View Slide

  34. UseCaseͷڞ༗ʹ͍ͭͯ
    • UseCaseͷڞ༗͸ͪΐͬͱ΍Γ͔͗͢΋͠Εͳ͍
    • F[_] ͕͍ͬͺ͍ͰΑ͘Θ͔Βͳ͍ʁ
    • ࣮ࡍ໰୊ɺJVM/JS ͰͦΕͧΕͰUseCase͸ඍົ
    ʹҧ͏
    • ແཧʹڞ௨ԽͤͣɺJVM/JS ͰUseCaseΛ෼͚Δ͔
    ΋
    © 2019 CyberAgent, Inc. 34/35

    View Slide

  35. Scala.js ಋೖͷॴײ
    • Scala.js ࣗମ͸׬શʹ 1.0 ΫΦϦςΟ͸͋Δ
    • पลϥΠϒϥϦ΋ॆ࣮ (Pure Scala ͳϥΠϒϥϦ͸
    ΄΅࢖͑Δ)
    • ࠓճͷΑ͏ʹ༻్͕ϋϚΕ͹͘͢͝༗༻
    • Ͱ΋WebϑϩϯτΤϯυΛScala.jsͰॻ͜͏ͱ͸·
    ͩࢥΘͳ͍
    © 2019 CyberAgent, Inc. 35/35

    View Slide