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

De2cd22cd6242773153ee76de1c9ecdb?s=47 AGAWA Koji
December 12, 2019

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

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

De2cd22cd6242773153ee76de1c9ecdb?s=128

AGAWA Koji

December 12, 2019
Tweet

Transcript

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

    Inc. © 2019 CyberAgent, Inc. 1/35
  2. Ѩ઒ ߞ࢘ ςοΫϦʔυ @ൢଅαΠωʔδAd • ۀ຿ྺ 14 ೥ / Scala

    ྺ 5 ೥ • ήʔϜ޷͖ (Steam ϝΠϯ) ࠷ۙ͸ࣨ಺αΠΫϦϯάΛؤ ுͬͯ·͢ɻ © 2019 CyberAgent, Inc. 2/35
  3. ൢଅαΠωʔδAd © 2019 CyberAgent, Inc. 3/35

  4. © 2019 CyberAgent, Inc. 4/35

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

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

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

    CyberAgent, Inc. 7/35
  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
  9. Scala.js © 2019 CyberAgent, Inc. 9/35

  10. Scala.js • Scala ͔Β JavaScript ʹίϯύΠϧ͢Δ΍ͭ • 2013೥͔Β։ൃ͕ଓ͍͍ͯΔ • ࠷ۙ

    1.0-RC ͕ग़ͯɺ2020೥1݄ʹ 1.0 ϦϦʔε༧ ఆ © 2019 CyberAgent, Inc. 10/35
  11. ݸਓతͳҹ৅ • ScalaͰJSॻ͚Δͷ͸໘ന͍ • JSΤίγεςϜͱͷ૬ੑΛߟ͑ͨΒTypeScript͕ ແ೉Ͱ͸ʁ • มԽͷܹ͍͠ React, Vue,

    ... ͳͲΛ࢖͑Δͷ͔ • ࢖͍ॴ͕೉ͦ͠͏ © 2019 CyberAgent, Inc. 11/35
  12. ࠓճ࠾༻ͨ͠ཧ༝ • υϝΠϯ૚ΛαʔόʔͱαΠωʔδͰڞ༗ͨ͠ ͔ͬͨ • αʔόʔ͸ Scala Ͱ֬ఆɺαΠωʔδ͸ Android Ͱ֬ఆɺͱ͍͏ঢ়گ

    • Android ΑΓ(׳Ε͍ͯΔ) Web ٕज़ʹد͔ͤͨͬ ͨ → Scala.js ͕ϕετϚονͰ͸ʂʁ © 2019 CyberAgent, Inc. 12/35
  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
  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
  15. Scala.js ͷಋೖ • sbt Ͱ fastOptJS λεΫΛ࣮ߦ͢Δͱ js ͕ॻ͖ग़ ͞ΕΔ

    $ sbt fastOptJS Ҏ্ɺ؆୯Ͱ͢Ͷ © 2019 CyberAgent, Inc. 15/35
  16. Scala.jsಋೖ·Ͱ • औΓֻ͔͔ͬͯΒ 2 ೔ఔ౓Ͱ׬ྃ • ΋ͱ΋ͱ Scala.js ͷ͜ͱΛҰ੾ߟྀ͍ͯ͠ͳ͍ ίʔυϕʔε

    • υϝΠϯ૚͸ϥΠϒϥϦґଘ͕গͳ͔ͬͨ © 2019 CyberAgent, Inc. 16/35
  17. AndroidͱScala.jsͷܨ͗ © 2019 CyberAgent, Inc. 17/35

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

  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
  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<String>): Unit { requestCacheContentsDelegate.apply(urls) } // publish ... } © 2019 CyberAgent, Inc. 20/35
  21. υϝΠϯ૚ͷશମ૾ © 2019 CyberAgent, Inc. 21/35

  22. SignageProgram ͜ΕΛαʔόʔͰ૊ΈཱͯɺAndroidଆͰར༻͢Δͷ ͕ओ໨త • Protocol Buffers Ͱαʔόʔ͔ΒAndroid΁ૹ৴ • Scala ͰγϦΞϥΠζͯ͠ɺScala.js

    ͰσγϦΞϥ Πζ • ྆ଆͱ΋ ScalaPB Ͱ؆୯ʹͰ͖ͨ © 2019 CyberAgent, Inc. 22/35
  23. Scala.js ར༻ͷ޻෉ © 2019 CyberAgent, Inc. 23/35

  24. Long ܕͷར༻ʹؾΛ͚ͭΔ • JVM ͷ Long ͸ 63bit • JS

    ͷ Number ͸ Double ૬౰ͳͷͰԾ਺෦ 52bit • LocalTime#toNanoOfDay() Ͱਫ਼౓མͪʹૺ۰ • ࣮ߦ࣌ΤϥʔʹͳΔ • toSecondOfDay() + getNano() ͰରԠ © 2019 CyberAgent, Inc. 24/35
  25. Java API ͸جૅతͳ෦෼ͷΈར༻Մ ೳ • ৄࡉ͸ https://github.com/scala-js/scala-js/tree/ master/javalib/src/main/scala/java Λࢀর ©

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

    time" ͷར༻Ͱղܾ © 2019 CyberAgent, Inc. 26/35
  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
  28. UseCase Λ JVM/JS Ͱڞ༗͢Δ • DisplayContentUseCase ͸ JVM/JS Ͱڞ༗͍ͨ͠ •

    JS Ͱ͸αΠωʔδʹίϯςϯπΛදࣔ͢Δ • JVM Ͱ͸දࣔ͞ΕΔίϯςϯπΛϓϨϏϡʔ͢ Δ • UseCase ͕ڞ௨ͳͷͰ LocationRepository ͳͲ ͷ Repository ΋ JVM ͱ JS ͰಉҰ trait Λ࢖͍ͨ © 2019 CyberAgent, Inc. 28/35
  29. ࠷ॳͷܗ trait LocationRepository { def findById(id: LocationId): ConnectionIO[Option[Location]] } •

    doobie.free.connection.ConnectionIO • doobie (JDBC ϥΠϒϥϦ) ͷ JS ൛͸ଘࡏ͠ͳ ͍(౰વ) • JS ͔Β ConnectionIO ܕΛར༻Ͱ͖ͳ͍ • ͳΜΒ͔ͷந৅Խ͕ඞཁ © 2019 CyberAgent, Inc. 29/35
  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
  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
  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
  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
  34. UseCaseͷڞ༗ʹ͍ͭͯ • UseCaseͷڞ༗͸ͪΐͬͱ΍Γ͔͗͢΋͠Εͳ͍ • F[_] ͕͍ͬͺ͍ͰΑ͘Θ͔Βͳ͍ʁ • ࣮ࡍ໰୊ɺJVM/JS ͰͦΕͧΕͰUseCase͸ඍົ ʹҧ͏

    • ແཧʹڞ௨ԽͤͣɺJVM/JS ͰUseCaseΛ෼͚Δ͔ ΋ © 2019 CyberAgent, Inc. 34/35
  35. Scala.js ಋೖͷॴײ • Scala.js ࣗମ͸׬શʹ 1.0 ΫΦϦςΟ͸͋Δ • पลϥΠϒϥϦ΋ॆ࣮ (Pure

    Scala ͳϥΠϒϥϦ͸ ΄΅࢖͑Δ) • ࠓճͷΑ͏ʹ༻్͕ϋϚΕ͹͘͢͝༗༻ • Ͱ΋WebϑϩϯτΤϯυΛScala.jsͰॻ͜͏ͱ͸· ͩࢥΘͳ͍ © 2019 CyberAgent, Inc. 35/35