Speaker Deck
Speaker Deck Pro
Sign in
Sign up
for free
Scala.jsとAndroidでドメイン層を共有しよう / Scala.js and Android
AGAWA Koji
December 12, 2019
Programming
0
240
Scala.jsとAndroidでドメイン層を共有しよう / Scala.js and Android
社内カンファレンスで発表した資料です。
AGAWA Koji
December 12, 2019
Tweet
Share
More Decks by AGAWA Koji
See All by AGAWA Koji
atty303
2
1.3k
atty303
4
2k
atty303
0
670
atty303
0
540
atty303
0
230
atty303
3
150
atty303
1
1.6k
atty303
0
260
atty303
0
770
Other Decks in Programming
See All in Programming
malvinstn
1
610
saki4869
0
170
yshrsmz
1
440
itosho525
0
340
ufoo68
1
170
kenmaz
1
100
nanimonodemonai
2
1.3k
kazaman97
0
160
hanhan1978
0
290
77web
0
200
atskimura
0
290
rshindo
2
290
Featured
See All Featured
brad_frost
156
6.4k
trishagee
20
2k
maltzj
500
36k
lemiorhan
626
42k
roundedbygravity
241
21k
matthewcrist
73
7.5k
pedronauck
652
110k
jakevdp
774
200k
trallard
13
640
chrislema
173
14k
geeforr
332
29k
cherdarchuk
71
260k
Transcript
Scala.jsͰAndroidͱυϝΠ ϯΛڞ༗͠Α͏ Ѩ ߞ࢘ @atty303 AI tech studio / CyberAgent,
Inc. © 2019 CyberAgent, Inc. 1/35
Ѩ ߞ࢘ ςοΫϦʔυ @ൢଅαΠωʔδAd • ۀྺ 14 / Scala
ྺ 5 • ήʔϜ͖ (Steam ϝΠϯ) ࠷ۙࣨαΠΫϦϯάΛؤ ுͬͯ·͢ɻ © 2019 CyberAgent, Inc. 2/35
ൢଅαΠωʔδAd © 2019 CyberAgent, Inc. 3/35
© 2019 CyberAgent, Inc. 4/35
৽͍͠ސ٬ମݧͱ࣮ళ ฮͷՁΛ͢Δ Λϛογϣϯʹܝ͛ɺίϯϏχ ΤϯεετΞɾυϥοάετ Ξɾεʔύʔͱ͍࣮ͬͨళฮͷ σδλϧԽɾϝσΟΞԽΛɺσ δλϧαΠωʔδͱίϯςϯπ ʗࠂ৴γεςϜΛ༻͍࣮ͯ ݱ͠·͢ɻ ©
2019 CyberAgent, Inc. 5/35
αΠωʔδͷٕज़ελοΫ • Android 6.0 (API 23) • Kotlin • MediaPlayer
+ TextureView • WebView + Scala.js © 2019 CyberAgent, Inc. 6/35
ϨΠϠʔͱٕज़ • Kotlin υϥΠόͷΈ • υϝΠϯ͓ΑͼΞϓϦέʔ γϣϯ Scala.js © 2019
CyberAgent, Inc. 7/35
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
Scala.js © 2019 CyberAgent, Inc. 9/35
Scala.js • Scala ͔Β JavaScript ʹίϯύΠϧ͢Δͭ • 2013͔Β։ൃ͕ଓ͍͍ͯΔ • ࠷ۙ
1.0-RC ͕ग़ͯɺ20201݄ʹ 1.0 ϦϦʔε༧ ఆ © 2019 CyberAgent, Inc. 10/35
ݸਓతͳҹ • ScalaͰJSॻ͚Δͷ໘ന͍ • JSΤίγεςϜͱͷ૬ੑΛߟ͑ͨΒTypeScript͕ ແͰʁ • มԽͷܹ͍͠ React, Vue,
... ͳͲΛ͑Δͷ͔ • ͍ॴ͕ͦ͠͏ © 2019 CyberAgent, Inc. 11/35
ࠓճ࠾༻ͨ͠ཧ༝ • υϝΠϯΛαʔόʔͱαΠωʔδͰڞ༗ͨ͠ ͔ͬͨ • αʔόʔ Scala Ͱ֬ఆɺαΠωʔδ Android Ͱ֬ఆɺͱ͍͏ঢ়گ
• Android ΑΓ(׳Ε͍ͯΔ) Web ٕज़ʹد͔ͤͨͬ ͨ → Scala.js ͕ϕετϚονͰʂʁ © 2019 CyberAgent, Inc. 12/35
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
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
Scala.js ͷಋೖ • sbt Ͱ fastOptJS λεΫΛ࣮ߦ͢Δͱ js ͕ॻ͖ग़ ͞ΕΔ
$ sbt fastOptJS Ҏ্ɺ؆୯Ͱ͢Ͷ © 2019 CyberAgent, Inc. 15/35
Scala.jsಋೖ·Ͱ • औΓֻ͔͔ͬͯΒ 2 ఔͰྃ • ͱͱ Scala.js ͷ͜ͱΛҰߟྀ͍ͯ͠ͳ͍ ίʔυϕʔε
• υϝΠϯϥΠϒϥϦґଘ͕গͳ͔ͬͨ © 2019 CyberAgent, Inc. 16/35
AndroidͱScala.jsͷܨ͗ © 2019 CyberAgent, Inc. 17/35
interop KotlinͷυϥΠόͱScala.jsͷΞϓϦέʔγϣϯ Λܨ͙ύʔπ © 2019 CyberAgent, Inc. 18/35
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
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
υϝΠϯͷશମ૾ © 2019 CyberAgent, Inc. 21/35
SignageProgram ͜ΕΛαʔόʔͰΈཱͯɺAndroidଆͰར༻͢Δͷ ͕ओత • Protocol Buffers Ͱαʔόʔ͔ΒAndroidૹ৴ • Scala ͰγϦΞϥΠζͯ͠ɺScala.js
ͰσγϦΞϥ Πζ • ྆ଆͱ ScalaPB Ͱ؆୯ʹͰ͖ͨ © 2019 CyberAgent, Inc. 22/35
Scala.js ར༻ͷ © 2019 CyberAgent, Inc. 23/35
Long ܕͷར༻ʹؾΛ͚ͭΔ • JVM ͷ Long 63bit • JS
ͷ Number Double ૬ͳͷͰԾ෦ 52bit • LocalTime#toNanoOfDay() Ͱਫ਼མͪʹૺ۰ • ࣮ߦ࣌ΤϥʔʹͳΔ • toSecondOfDay() + getNano() ͰରԠ © 2019 CyberAgent, Inc. 24/35
Java API جૅతͳ෦ͷΈར༻Մ ೳ • ৄࡉ https://github.com/scala-js/scala-js/tree/ master/javalib/src/main/scala/java Λࢀর ©
2019 CyberAgent, Inc. 25/35
Java API Ͱࠓճࠔͬͨͱ͜Ζ • java.time ύοέʔδ͕σϑΥϧτͰ͑ͳ͍ • "com.zoepepper" %%% "scalajs-jsjoda-as-java-
time" ͷར༻Ͱղܾ © 2019 CyberAgent, Inc. 26/35
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
UseCase Λ JVM/JS Ͱڞ༗͢Δ • DisplayContentUseCase JVM/JS Ͱڞ༗͍ͨ͠ •
JS ͰαΠωʔδʹίϯςϯπΛදࣔ͢Δ • JVM Ͱදࣔ͞ΕΔίϯςϯπΛϓϨϏϡʔ͢ Δ • UseCase ͕ڞ௨ͳͷͰ LocationRepository ͳͲ ͷ Repository JVM ͱ JS ͰಉҰ trait Λ͍ͨ © 2019 CyberAgent, Inc. 28/35
࠷ॳͷܗ trait LocationRepository { def findById(id: LocationId): ConnectionIO[Option[Location]] } •
doobie.free.connection.ConnectionIO • doobie (JDBC ϥΠϒϥϦ) ͷ JS ൛ଘࡏ͠ͳ ͍(વ) • JS ͔Β ConnectionIO ܕΛར༻Ͱ͖ͳ͍ • ͳΜΒ͔ͷநԽ͕ඞཁ © 2019 CyberAgent, Inc. 29/35
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
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
རศੑͷͨΊʹ 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
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
UseCaseͷڞ༗ʹ͍ͭͯ • UseCaseͷڞ༗ͪΐͬͱΓ͔͗͢͠Εͳ͍ • F[_] ͕͍ͬͺ͍ͰΑ͘Θ͔Βͳ͍ʁ • ࣮ࡍɺJVM/JS ͰͦΕͧΕͰUseCaseඍົ ʹҧ͏
• ແཧʹڞ௨ԽͤͣɺJVM/JS ͰUseCaseΛ͚Δ͔ © 2019 CyberAgent, Inc. 34/35
Scala.js ಋೖͷॴײ • Scala.js ࣗମશʹ 1.0 ΫΦϦςΟ͋Δ • पลϥΠϒϥϦॆ࣮ (Pure
Scala ͳϥΠϒϥϦ ΄΅͑Δ) • ࠓճͷΑ͏ʹ༻్͕ϋϚΕ͘͢͝༗༻ • ͰWebϑϩϯτΤϯυΛScala.jsͰॻ͜͏ͱ· ͩࢥΘͳ͍ © 2019 CyberAgent, Inc. 35/35