Scala on Android Tom Adams @tomjadams Me SVP of Polyglot - - PowerPoint PPT Presentation

scala on android
SMART_READER_LITE
LIVE PREVIEW

Scala on Android Tom Adams @tomjadams Me SVP of Polyglot - - PowerPoint PPT Presentation

Scala on Android Tom Adams @tomjadams Me SVP of Polyglot Engineering @ Cogent Rails, iOS, etc. Co-Founder & CTO Oomph 4 shipping + 1 prototype Android app > 100 bespoke iOS apps ~250 iOS apps on Oomph platform


slide-1
SLIDE 1

Scala on Android

Tom Adams
 @tomjadams

slide-2
SLIDE 2
slide-3
SLIDE 3

Me

  • SVP of Polyglot Engineering @ Cogent
  • Rails, iOS, etc.
  • Co-Founder & CTO Oomph
  • 4 shipping + 1 prototype Android app
  • > 100 bespoke iOS apps
  • ~250 iOS apps on Oomph platform
  • Enterprise Java, HPC, “big data” background
  • Founder of BFPG
slide-4
SLIDE 4

What?

  • Experiences building a native Android app
  • Oomph Viewer, Subaru Symmetry, etc.
  • Not looking at Phonegap, Ximian, Titanium, etc.
  • Not looking at “non-Scala” problems
  • Not teaching you Scala
  • Lots of code, the deck will be available
slide-5
SLIDE 5

App Goals

  • Oomph - digital publishing, think mags on iPads
  • A technical proof of concept client for Android
  • Build as a platform not a single app
  • Develop in parallel with server
  • No Java
  • Take advantage of Scala language & library support
slide-6
SLIDE 6
slide-7
SLIDE 7

The Good

slide-8
SLIDE 8

Why Scala?

  • Had done Java before, don’t want to use again
  • “Better Java”
  • Succinct/less boilerplate - closures, type classes, type aliases, type

inference, no semi-colons, no ‘.’

  • Features - immutability, equational reasoning, functions, case

classes, implicits, packages, mixins, currying/partial application, etc.

  • Standard & other library support - option, either, future, etc.
  • Cool stuff! scalaz, actors, higher-kinds, etc.
  • Share concepts/code/DB schema with server
  • Monadic code FTW!
slide-9
SLIDE 9

Example 1 - Java

context.runOnUiThread(new ¡Runnable() ¡{ ¡ ¡ ¡@Override ¡ ¡ ¡public ¡void ¡run() ¡{ ¡ ¡ ¡ ¡ ¡dialog.dismiss(); ¡ ¡ ¡ ¡ ¡String ¡standaloneStartUrl ¡= ¡"file://" ¡+ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡context.getFilesDir() ¡+ ¡"/content/index.html"; ¡ ¡ ¡ ¡ ¡Intent ¡next ¡= ¡new ¡Intent(context, ¡IssueViewerActivity.class); ¡ ¡ ¡ ¡ ¡next.putExtra("start_url", ¡standaloneStartUrl); ¡ ¡ ¡ ¡ ¡context.startActivity(next); ¡ ¡ ¡ ¡ ¡context.finish(); ¡ ¡ ¡} ¡ });

slide-10
SLIDE 10

Example 1 - Scala

context.runOnUiThread ¡{ ¡ ¡ ¡dialog.dismiss() ¡ ¡ ¡val ¡standaloneStartUrl ¡= ¡"file://" ¡+ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡context.getFilesDir ¡+ ¡"/content/index.html" ¡ ¡ ¡val ¡next ¡= ¡new ¡Intent(context, ¡classOf[IssueViewerActivity]) ¡ ¡ ¡next.putExtra("start_url", ¡standaloneStartUrl) ¡ ¡ ¡context.startActivity(next) ¡ ¡ ¡context.finish() ¡ }

slide-11
SLIDE 11

Example 2 - Java

Button ¡button ¡= ¡new ¡Button(context); ¡ button.setText("Greet"); ¡ button.setOnClickListener(new ¡OnClickListener() ¡{ ¡

¡ ¡@Override ¡ ¡ ¡public ¡void ¡onClick(View ¡v) ¡{ ¡

¡ ¡ ¡ ¡Toast.makeText( ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡context, ¡"Hello!", ¡Toast.LENGTH_SHORT).show(); ¡ ¡ ¡} ¡ }); ¡ layout.addView(button);

slide-12
SLIDE 12

Example 2 - Scala

val ¡button ¡= ¡new ¡Button(context) ¡ button.setText("Greet") ¡ button.setOnClickListener(new ¡OnClickListener() ¡{ ¡ ¡ ¡def ¡onClick(v: ¡View) ¡{ ¡ ¡ ¡ ¡ ¡Toast.makeText( ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡context, ¡"Hello!", ¡Toast.LENGTH_SHORT).show() ¡ ¡ ¡} ¡ }) ¡ layout.addView(button)

slide-13
SLIDE 13

Example 2 - Scala

val ¡button ¡= ¡new ¡Button(context) ¡ button.setText("Greet") ¡ button.setOnClickListener(Toast.makeText( ¡ ¡ ¡ ¡ ¡context, ¡"Hello!", ¡Toast.LENGTH_SHORT).show()) ¡ layout.addView(button)

slide-14
SLIDE 14

Example 2 - Scala(OID)

SButton("Greet", ¡toast("Hello!"))

slide-15
SLIDE 15

Example 2 - Scala(OID)

SButton("Greet", ¡toast("Hello!"))

slide-16
SLIDE 16

Objections

  • “Functions slow down VM”
  • “Too many small classes”
  • Not a problem in practice (YMMV)
  • Can proguard away
  • Language issues…
slide-17
SLIDE 17

Scala ≈ swift

slide-18
SLIDE 18

Toolchain

  • sbt
  • Android Plugin [1]
  • Scalastyle - stylechecker
  • IntelliJ w/ Scala plugin
  • TeamCity

[1] https://github.com/pfn/android-sdk-plugin

slide-19
SLIDE 19

sbT

  • “Simple” build tool
  • Native Scala build tool
  • Not great, but better than alternatives
  • Supported by TypeSafe
slide-20
SLIDE 20

sbt Plugin

  • Most mature at the time
  • Requires giter8
  • All the things you’d need: emulator & device

support, signing, tests, etc.

slide-21
SLIDE 21

IntelliJ

slide-22
SLIDE 22

IntelliJ

  • Awesome IDE
  • Multiple platforms - RubyMine, AppCode, PHP Storm,

etc.

  • Familiar
  • Decent support for Scala & sbt
slide-23
SLIDE 23

Other choices

  • Eclipse (?)
  • Android Studio (IntelliJ)
  • Android SDK Plugin [1]
  • Scaloid template [2] (uses [1])

[1] https://github.com/pfn/android-sdk-plugin [2] https://github.com/pocorall/hello-scaloid-sbt

slide-24
SLIDE 24

Scaloid

  • Take advantage of language features
  • Simplifies common patterns
  • Alerts, inter-activity comms
  • DSL for UI building
slide-25
SLIDE 25

Scaloid

var ¡connectivityListener: ¡BroadcastReceiver ¡= ¡null ¡

  • def ¡onResume() ¡{ ¡

¡ ¡super.onResume() ¡ ¡ ¡connectivityListener ¡= ¡new ¡BroadcastReceiver ¡{ ¡ ¡ ¡ ¡ ¡def ¡onReceive(context: ¡Context, ¡intent: ¡Intent) ¡{ ¡ ¡ ¡ ¡ ¡ ¡doSomething() ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡} ¡ ¡ ¡ ¡registerReceiver(connectivityListener, ¡ ¡ ¡ ¡ ¡ ¡ ¡new ¡IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)) ¡ } ¡

  • def ¡onPause() ¡{ ¡

¡ ¡unregisterReceiver(connectivityListener) ¡ ¡ ¡super.onPause() ¡ }

slide-26
SLIDE 26

Scaloid

broadcastReceiver(ConnectivityManager.CONNECTIVITY_ACTION) ¡{ ¡ ¡ ¡(context, ¡intent) ¡=> ¡doSomething() ¡ }

slide-27
SLIDE 27

Scaloid

new ¡AsyncTask[String, ¡Void, ¡String] ¡{ ¡ ¡ ¡def ¡doInBackground(params: ¡Array[String]) ¡= ¡{ ¡ ¡ ¡ ¡ ¡doAJobTakeSomeTime(params) ¡ ¡ ¡} ¡

  • ¡ ¡override ¡def ¡onPostExecute(result: ¡String) ¡{ ¡

¡ ¡ ¡ ¡alert("Done!", ¡result) ¡ ¡ ¡} ¡ }.execute("param")

slide-28
SLIDE 28

Scaloid

Future ¡{ ¡ ¡ ¡val ¡result ¡= ¡doAJobTakeSomeTime(params) ¡ ¡ ¡runOnUiThread(alert("Done!", ¡result)) ¡ }

slide-29
SLIDE 29

scalaz

  • An extension to the core Scala library for

functional programming

  • New datatypes (Validation, NonEmptyList, etc.)
  • Extensions to standard classes (OptionOps,

ListOps, etc.)

  • Implementations of general functions you need

(ad-hoc polymorphism, traits + implicits)

slide-30
SLIDE 30

Argonaut

val ¡input ¡= ¡""" ¡ ¡ ¡[ ¡ ¡ ¡ ¡ ¡{ ¡"name": ¡"Mark", ¡"age": ¡191 ¡}, ¡ ¡ ¡ ¡ ¡{ ¡"name": ¡"Fred", ¡"age": ¡33, ¡"greeting": ¡"hey ¡ho, ¡lets ¡go!" ¡}, ¡ ¡ ¡ ¡ ¡{ ¡"name": ¡"Barney", ¡"age": ¡35, ¡"address": ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡"street": ¡"rock ¡street", ¡"number": ¡10, ¡"post_code": ¡2039 ¡ ¡ ¡ ¡ ¡}} ¡ ¡ ¡] ¡ """ ¡ ¡ ¡ val ¡people ¡= ¡input.decodeOption[List[Person]].getOrElse(Nil) ¡ ¡ ¡ val ¡nice ¡= ¡people.map(person ¡=> ¡ ¡ ¡ ¡ ¡person.copy(greeting ¡= ¡person.greeting.orElse(Some("Hello ¡good ¡sir!")))) ¡ ¡ ¡ val ¡result ¡= ¡nice.asJson ¡ println(result.spaces4) ¡ ¡ ¡ assert(result.array.exists(_.length ¡== ¡3))

slide-31
SLIDE 31

Specs2

final ¡class ¡HelloWorldSpec ¡extends ¡Specification ¡{ ¡ ¡ ¡"The ¡'Hello ¡world' ¡string" ¡should ¡{ ¡ ¡ ¡ ¡ ¡"contain ¡11 ¡characters" ¡in ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡"Hello ¡world" ¡must ¡have ¡size(11) ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡"start ¡with ¡'Hello'" ¡in ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡"Hello ¡world" ¡must ¡startWith("Hello") ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡"end ¡with ¡'world'" ¡in ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡"Hello ¡world" ¡must ¡endWith("world") ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡} ¡ }

slide-32
SLIDE 32

Slick

  • Type safe DB access library for Scala
  • Supported by TypeSafe
  • Works well on device or server
  • Had used in several projects before (ScalaQuery)
  • Added in integration with Android DB lifecycle
  • Added migration support
slide-33
SLIDE 33

Slick

class ¡Coffees(tag: ¡Tag) ¡ ¡ ¡ ¡ ¡ ¡extends ¡Table[(String, ¡Double)](tag, ¡"COFFEES") ¡{ ¡ ¡ ¡def ¡name ¡= ¡column[String]("COF_NAME", ¡O.PrimaryKey) ¡ ¡ ¡def ¡price ¡= ¡column[Double]("PRICE") ¡ ¡ ¡def ¡* ¡= ¡(name, ¡price) ¡ } ¡ ¡ ¡ val ¡coffees ¡= ¡TableQuery[Coffees] ¡ coffees.map(_.name) ¡ coffees.filter(_.price ¡< ¡10.0) ¡ val ¡coffeeNames: ¡Seq[Double] ¡= ¡coffees.map(_.price).list ¡ coffees.filter(_.price ¡< ¡10.0).sortBy(_.name).map(_.name)

slide-34
SLIDE 34

Google

  • Access to the infrastructure Google provide
  • https://www.buzzingandroid.com/2013/01/

push-messages-on-android-and-iphone/

slide-35
SLIDE 35

The App

slide-36
SLIDE 36

The App

  • 3 activities
  • Main
  • Library Screen
  • Content Viewer
slide-37
SLIDE 37

Main Activity

final ¡class ¡MainActivity ¡extends ¡OomphActivity ¡with ¡Messaging ¡{ ¡ ¡ ¡lazy ¡val ¡onInstall ¡= ¡new ¡OnInstall(this) ¡ ¡ ¡ ¡ ¡val ¡onError: ¡HttpFailure ¡=> ¡Unit ¡= ¡{ ¡v ¡=> ¡Log.i("Oomph", ¡v.toString)} ¡ ¡ ¡ ¡ ¡val ¡initializer ¡= ¡config.isStandalone ¡? ¡standalone ¡| ¡library ¡ ¡ ¡ ¡ ¡onCreate(OomphDatabase.migrate(this)) ¡ ¡ ¡onCreate(setContentView(R.layout.main)) ¡ ¡ ¡onCreate(initializer.configure(this, ¡config)) ¡ ¡ ¡onStart(register()) ¡ ¡ ¡ ¡ ¡def ¡register() ¡{ ¡ ¡ ¡ ¡ ¡val ¡installDetails ¡= ¡onInstall.installationDetails() ¡ ¡ ¡ ¡ ¡registerForGCM ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡// ¡register ¡-­‑ ¡see ¡later ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡} ¡ }

slide-38
SLIDE 38

List Activity

final ¡class ¡LibraryListActivity ¡ ¡ ¡ ¡ ¡ ¡extends ¡OomphActivity ¡with ¡OomphDatabase.ActivityDB ¡{ ¡ ¡ ¡onCreate({ ¡ ¡ ¡ ¡ ¡val ¡fragmentManager ¡= ¡getFragmentManager ¡ ¡ ¡ ¡ ¡val ¡fragmentTransaction ¡= ¡fragmentManager.beginTransaction ¡ ¡ ¡ ¡ ¡fragmentTransaction.add(android.R.id.content, ¡new ¡AllIssues, ¡"") ¡ ¡ ¡ ¡ ¡fragmentTransaction.commit ¡ ¡ ¡}) ¡ ¡ ¡ ¡ ¡onStart({startService( ¡ ¡ ¡ ¡ ¡new ¡Intent(this, ¡classOf[GoAndGetEmTigerService]))}) ¡ }

slide-39
SLIDE 39

Issue Viewer Activity

final ¡class ¡IssueViewerActivity ¡extends ¡OomphActivity ¡{ ¡ ¡ ¡lazy ¡val ¡oomphUi: ¡WebView ¡= ¡findView(TR.oomphUi) ¡ ¡ ¡var ¡startUrl: ¡String ¡= ¡_ ¡ ¡ ¡onCreate ¡{ ¡ ¡ ¡ ¡ ¡startUrl ¡= ¡getIntent.getExtras.getString("start_url") ¡ ¡ ¡ ¡ ¡setContentView(R.layout.main) ¡ ¡ ¡ ¡ ¡if ¡(!config.isRelease) ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡List(TR.refresh_button, ¡TR.select_oomph_issues_list) ¡foreach ¡(b ¡=> ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡Option(findView(b)).map(_.setVisibility(View.VISIBLE))) ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡OomphUi.setup(this, ¡oomphUi, ¡getWindowManager) ¡ ¡ ¡ ¡ ¡oomphUi.loadUrl(startUrl) ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡def ¡webViewReload(view: ¡View) ¡{ ¡ ¡ ¡ ¡ ¡oomphUi.loadUrl(startUrl) ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡def ¡selectLibraryList(view: ¡View) ¡{ ¡ ¡ ¡ ¡ ¡startActivity(new ¡Intent(this, ¡classOf[LibraryListActivity])) ¡ ¡ ¡} ¡ }

slide-40
SLIDE 40

JSON Parsing

case ¡class ¡Installation(uuid: ¡String) ¡ ¡ ¡

  • bject ¡Installation ¡{ ¡

¡ ¡implicit ¡val ¡codec ¡= ¡casecodec1(Installation.apply, ¡Installation.unapply)("uuid") ¡ } ¡ ¡ ¡ case ¡class ¡ApprovedDownload(productId: ¡String, ¡url: ¡String, ¡md5: ¡String) ¡ ¡ ¡

  • bject ¡ApprovedDownload ¡{ ¡

¡ ¡implicit ¡val ¡codec ¡= ¡{ ¡ ¡ ¡ ¡ ¡val ¡c ¡= ¡casecodec3(ApprovedDownload.apply, ¡ApprovedDownload.unapply) ¡ ¡ ¡ ¡ ¡c("productId", ¡"url", ¡“md5") ¡ ¡ ¡} ¡ }

slide-41
SLIDE 41

API CALLING

case ¡class ¡OomphApi(client: ¡OomphClient) ¡{ ¡ ¡ ¡def ¡register(registrationId: ¡String, ¡uuid: ¡String)(f: ¡ApiResponder[Unit]) ¡{ ¡ ¡ ¡ ¡ ¡client.post("register", ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡Map("registration_id" ¡-­‑> ¡registrationId, ¡"device_uuid" ¡-­‑> ¡uuid), ¡f) ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡def ¡issues(f: ¡ApiResponder[List[RemoteIssue]]) ¡{ ¡ ¡ ¡ ¡ ¡implicit ¡val ¡decodeI ¡= ¡casecodec4(RemoteIssue, ¡RemoteIssue.unapply)( ¡ ¡ ¡ ¡ ¡ ¡ ¡"product_id", ¡ ¡ ¡ ¡ ¡ ¡ ¡"name", ¡ ¡ ¡ ¡ ¡ ¡ ¡"cover_url", ¡ ¡ ¡ ¡ ¡ ¡ ¡"content_md5") ¡ ¡ ¡ ¡ ¡client.get("issues", ¡Map.empty, ¡f) ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡def ¡download(productId: ¡String)(f: ¡ApiResponder[ApprovedDownload]) ¡{ ¡ ¡ ¡ ¡ ¡val ¡decoder ¡= ¡jdecode2L((url: ¡String, ¡md5: ¡String) ¡=> ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ApprovedDownload(productId, ¡url,md5))("url", ¡"md5") ¡ ¡ ¡ ¡ ¡client.post("download", ¡Map("product_id" ¡-­‑> ¡productId), ¡f)(decoder) ¡ ¡ ¡} ¡ }

slide-42
SLIDE 42

Fetch & Store Data

  • bject ¡GoAndGetEmTiger ¡{ ¡

¡ ¡def ¡go[A](o: ¡OomphApi, ¡database: ¡ODB, ¡f: ¡() ¡=> ¡Unit) ¡{ ¡ ¡ ¡ ¡ ¡o.issues(ApiResponder(_ ¡=> ¡(), ¡{ ¡issues ¡=> ¡ ¡ ¡ ¡ ¡ ¡ ¡database.runSession(install(issues)) ¡ ¡ ¡ ¡ ¡ ¡ ¡f() ¡ ¡ ¡ ¡ ¡})) ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡def ¡install(issues: ¡List[RemoteIssue]): ¡DB[Unit] ¡= ¡for ¡{ ¡ ¡ ¡ ¡ ¡_ ¡<-­‑ ¡Issues.markAllInactive ¡ ¡ ¡ ¡ ¡_ ¡<-­‑ ¡issues.traverse_(recordOrUpdate) ¡ ¡ ¡} ¡yield ¡() ¡ ¡ ¡ ¡ ¡def ¡recordOrUpdate(newIssue: ¡RemoteIssue): ¡DB[Unit] ¡= ¡DB ¡{ ¡implicit ¡s ¡=> ¡ ¡ ¡ ¡ ¡val ¡byProductQuery ¡= ¡for ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡i ¡<-­‑ ¡Issues ¡if ¡i.productId ¡=== ¡newIssue.productId ¡ ¡ ¡ ¡ ¡} ¡yield ¡i.nextMd5 ¡~ ¡i.title ¡~ ¡i.imageUrl ¡~ ¡i.active ¡ ¡ ¡ ¡ ¡ ¡ ¡if ¡(Query(byProductQuery.length).first ¡> ¡0) ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡byProductQuery.update((newIssue.md5, ¡newIssue.title, ¡newIssue.coverUrl, ¡true)) ¡ ¡ ¡ ¡ ¡} ¡else ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡Issues.insert(newIssue.asIssue) ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡} ¡ }

slide-43
SLIDE 43

Futures

def ¡copyContent(context: ¡Activity): ¡Unit ¡= ¡{ ¡ ¡ ¡val ¡dialog ¡= ¡ProgressDialog.show(context, ¡"", ¡"Installing...", ¡true) ¡ ¡ ¡val ¡r ¡= ¡future ¡{ContentHelper.assetsToInternalStore(context)} ¡ ¡ ¡r ¡onComplete ¡{ ¡v ¡=> ¡ ¡ ¡ ¡ ¡v ¡match ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡case ¡TryFailure(e) ¡=> ¡e.printStackTrace() ¡ ¡ ¡ ¡ ¡ ¡ ¡case ¡_ ¡=> ¡() ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡context.runOnUiThread ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡dialog.dismiss() ¡ ¡ ¡ ¡ ¡ ¡ ¡val ¡next ¡= ¡new ¡Intent(context, ¡classOf[IssueViewerActivity]) ¡ ¡ ¡ ¡ ¡ ¡ ¡next.putExtra(“start_url", ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡"file://" ¡+ ¡context.getFilesDir ¡+ ¡"/content/index.html") ¡ ¡ ¡ ¡ ¡ ¡ ¡context.startActivity(next) ¡ ¡ ¡ ¡ ¡ ¡ ¡context.finish() ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡} ¡ }

slide-44
SLIDE 44

GCM

trait ¡Messaging ¡{ ¡self: ¡Context ¡with ¡Configured ¡=> ¡ ¡ ¡def ¡registerForGCM(f: ¡Throwable ¡\/ ¡String ¡=> ¡Unit) ¡{ ¡ ¡ ¡ ¡ ¡val ¡gcm ¡= ¡GoogleCloudMessaging.getInstance(this) ¡ ¡ ¡ ¡ ¡val ¡r ¡= ¡future ¡{ ¡gcm.register(config.gcmSenderId) ¡} ¡ ¡ ¡ ¡ ¡r ¡onSuccess ¡{ ¡case ¡v ¡=> ¡f(\/-­‑(v)) ¡} ¡ ¡ ¡ ¡ ¡r ¡onFailure ¡{ ¡case ¡e ¡=> ¡f(-­‑\/(e)) ¡} ¡ ¡ ¡} ¡ } ¡ ¡ ¡ val ¡onError: ¡HttpFailure ¡=> ¡Unit ¡= ¡{ ¡v ¡=> ¡Log.e("Oomph", ¡v.toString)} ¡ ¡ ¡ registerForGCM ¡{ ¡ ¡ ¡case ¡-­‑\/(e) ¡=> ¡Log.e("Oomph", ¡"Registration ¡failed: ¡%s" ¡format ¡e) ¡ ¡ ¡case ¡\/-­‑(v) ¡=> ¡{ ¡ ¡ ¡ ¡ ¡val ¡r ¡= ¡config.oomphApi.register(v, ¡installDetails.uuid) ¡ ¡ ¡ ¡ ¡r(ApiResponder(onError)) ¡{ ¡_ ¡=> ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡Log.i("Oomph", ¡"Registration ¡happened”) ¡ ¡ ¡ ¡ ¡}) ¡ ¡ ¡} ¡ }

slide-45
SLIDE 45

DB Mechanics - Migrations

  • bject ¡OomphDatabase ¡extends ¡AndroidDatabaseAccess ¡{ ¡

¡ ¡val ¡databaseName ¡= ¡"oomph" ¡ ¡ ¡ ¡ ¡val ¡migrations: ¡Migrations ¡= ¡Migrations(NonEmptyList( ¡ ¡ ¡ ¡ ¡Migration(1, ¡ ¡ ¡ ¡ ¡ ¡ ¡"""CREATE ¡TABLE ¡issues ¡( ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡_id ¡INTEGER ¡PRIMARY ¡KEY ¡AUTOINCREMENT, ¡product_id ¡TEXT ¡NOT ¡NULL, ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡title ¡TEXT ¡NOT ¡NULL, ¡image_url ¡TEXT ¡NOT ¡NULL, ¡installed_md5 ¡TEXT, ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡next_md5 ¡TEXT, ¡active ¡BIT ¡DEFAULT ¡0 ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡); ¡ ¡ ¡ ¡ ¡ ¡ ¡""" ¡ ¡ ¡ ¡ ¡) ¡ ¡ ¡)) ¡ }

slide-46
SLIDE 46

Monadic DB

case ¡class ¡DB[A](f: ¡Session ¡=> ¡A) ¡{ ¡ ¡ ¡def ¡map[B](ff: ¡A ¡=> ¡B): ¡DB[B] ¡= ¡DB(s ¡=> ¡ff(f(s))) ¡ ¡ ¡ ¡ ¡def ¡flatMap[B](ff: ¡A ¡=> ¡DB[B]): ¡DB[B] ¡= ¡DB(s ¡=> ¡ff(f(s)).f(s)) ¡ } ¡ ¡ ¡

  • bject ¡DB ¡{ ¡

¡ ¡implicit ¡def ¡instances: ¡Applicative[DB] ¡= ¡new ¡Applicative[DB] ¡{ ¡ ¡ ¡ ¡ ¡def ¡point[A](a: ¡=> ¡A): ¡DB[A] ¡= ¡DB(_ ¡=> ¡a) ¡ ¡ ¡ ¡ ¡ ¡ ¡def ¡ap[A, ¡B](fa: ¡=> ¡DB[A])(f: ¡=> ¡DB[A ¡=> ¡B]): ¡DB[B] ¡= ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡DB(function1Covariant.ap(fa.f)(f.f)) ¡ ¡ ¡} ¡ }

slide-47
SLIDE 47

Android Database Integration

  • bject ¡Database ¡{ ¡

¡ ¡def ¡open(databaseName: ¡String, ¡context: ¡Context): ¡Database ¡= ¡{ ¡ ¡ ¡ ¡ ¡val ¡path ¡= ¡context.getDatabasePath(databaseName).getCanonicalPath ¡ ¡ ¡ ¡ ¡val ¡url ¡= ¡"jdbc:sqldroid:%s".format(path) ¡ ¡ ¡ ¡ ¡val ¡database ¡= ¡SlickDatabase.forDriver(new ¡SQLDroidDriver(), ¡url) ¡ ¡ ¡ ¡ ¡ ¡ ¡new ¡Database ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡def ¡runSession[T](f: ¡DB[T]): ¡T ¡= ¡database.withSession(f.f) ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡def ¡runTransaction[T](f: ¡DB[T]): ¡T ¡= ¡database.withTransaction(f.f) ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡} ¡ }

slide-48
SLIDE 48

DB Mechanics

trait ¡AndroidDatabaseAccess ¡{ ¡ ¡ ¡val ¡databaseName: ¡String ¡ ¡ ¡val ¡migrations: ¡Migrations ¡ ¡ ¡ ¡ ¡def ¡migrate(c: ¡Context) ¡{ ¡Migrator.migrate(databaseName, ¡migrations, ¡c) ¡} ¡ ¡ ¡ ¡ ¡trait ¡ActivityDB ¡extends ¡Activity ¡with ¡DBState ¡{ ¡ ¡ ¡ ¡ ¡abstract ¡override ¡def ¡onCreate(b: ¡Bundle) ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡super.onCreate(b) ¡ ¡ ¡ ¡ ¡ ¡ ¡db ¡= ¡Database.open(databaseName, ¡this) ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡trait ¡IntentDB ¡extends ¡IntentService ¡with ¡DBState ¡{ ¡ ¡ ¡ ¡ ¡// ¡... ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡trait ¡FragmentDB ¡extends ¡Fragment ¡with ¡DBState ¡{ ¡ ¡ ¡ ¡ ¡// ¡... ¡ ¡ ¡} ¡ }

slide-49
SLIDE 49

Entity DefInition

case ¡class ¡RemoteIssue(productId: ¡String, ¡title: ¡String, ¡coverUrl: ¡String, ¡md5: ¡String) ¡{ ¡ ¡ ¡def ¡asIssue: ¡Issue ¡= ¡Issue(productId, ¡title, ¡coverUrl, ¡None, ¡md5, ¡true, ¡None) ¡ } ¡ ¡ ¡ case ¡class ¡Issue(productId: ¡String, ¡title: ¡String, ¡imageUrl: ¡String, ¡ ¡ ¡ ¡ ¡installedMd5: ¡Option[String], ¡nextMd5: ¡String, ¡active: ¡Boolean, ¡id: ¡Option[Int]) ¡ ¡ ¡

  • bject ¡Issues ¡extends ¡Table[Issue]("ISSUES") ¡{ ¡

¡ ¡def ¡id ¡= ¡column[Int]("_ID", ¡O.PrimaryKey, ¡O.AutoInc, ¡O.NotNull) ¡ ¡ ¡def ¡productId ¡= ¡column[String]("PRODUCT_ID", ¡O.NotNull) ¡ ¡ ¡def ¡title ¡= ¡column[String]("TITLE", ¡O.NotNull) ¡ ¡ ¡def ¡imageUrl ¡= ¡column[String]("IMAGE_URL", ¡O.NotNull) ¡ ¡ ¡def ¡installedMd5 ¡= ¡column[Option[String]]("INSTALLED_MD5") ¡ ¡ ¡def ¡nextMd5 ¡= ¡column[String]("NEXT_MD5") ¡ ¡ ¡def ¡active ¡= ¡column[Boolean]("ACTIVE", ¡O.Default(false)) ¡ ¡ ¡def ¡baseProjection ¡= ¡productId ¡~ ¡title ¡~ ¡imageUrl ¡~ ¡installedMd5 ¡~ ¡nextMd5 ¡~ ¡active ¡ ¡ ¡def ¡* ¡= ¡baseProjection ¡~ ¡id.? ¡<>(Issue, ¡Issue.unapply ¡_) ¡ ¡ ¡ ¡ ¡def ¡markAllInactive: ¡DB[Unit] ¡= ¡DB ¡{ ¡implicit ¡s ¡=> ¡this.map(_.active).update(false) ¡} ¡ }

slide-50
SLIDE 50

DB Usage

class ¡AllIssues ¡extends ¡LibraryListFragment ¡{ ¡ ¡ ¡def ¡query: ¡DB[List[Issue]] ¡= ¡Library.allIssues ¡ } ¡ ¡ ¡ class ¡InstalledIssues ¡extends ¡LibraryListFragment ¡{ ¡ ¡ ¡def ¡query: ¡DB[List[Issue]] ¡= ¡Library.installed ¡ }

slide-51
SLIDE 51

DB Usage - Read

  • bject ¡Library ¡{ ¡

¡ ¡def ¡allIssues: ¡DB[List[Issue]] ¡= ¡DB ¡{ ¡implicit ¡s ¡=> ¡ ¡ ¡ ¡ ¡ ¡ ¡val ¡q2 ¡= ¡for ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡i ¡<-­‑ ¡Issues ¡ ¡ ¡ ¡ ¡ ¡ ¡} ¡yield ¡i ¡ ¡ ¡ ¡ ¡ ¡ ¡q2.list ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡def ¡issueForProductId(pId: ¡String): ¡DB[Issue] ¡= ¡DB ¡{ ¡implicit ¡s ¡=> ¡ ¡ ¡ ¡ ¡ ¡ ¡val ¡q2 ¡= ¡for ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡i ¡<-­‑ ¡Issues ¡if ¡i.productId ¡=== ¡pId ¡ ¡ ¡ ¡ ¡ ¡ ¡} ¡yield ¡i ¡ ¡ ¡ ¡ ¡ ¡ ¡q2.first() ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡def ¡installed: ¡DB[List[Issue]] ¡= ¡DB ¡{ ¡implicit ¡s ¡=> ¡ ¡ ¡ ¡ ¡ ¡ ¡val ¡q2 ¡= ¡for ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡i ¡<-­‑ ¡Issues ¡if ¡i.installedMd5 ¡=== ¡i.nextMd5 ¡ ¡ ¡ ¡ ¡ ¡ ¡} ¡yield ¡i ¡ ¡ ¡ ¡ ¡ ¡ ¡q2.list ¡ ¡ ¡ ¡ ¡} ¡ }

slide-52
SLIDE 52

DB Access - Write

  • bject ¡Library ¡{ ¡

¡ ¡def ¡insertIssues(issues: ¡List[Issue]): ¡DB[Unit] ¡= ¡DB ¡{ ¡implicit ¡s ¡=> ¡ ¡ ¡ ¡ ¡issues.map(i ¡=> ¡Issues.insert(i)) ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡def ¡updateIssueNextMd5(issuePid: ¡String, ¡nextMd5: ¡String): ¡DB[Unit] ¡= ¡{ ¡ ¡ ¡ ¡ ¡DB ¡{ ¡implicit ¡s ¡=> ¡ ¡ ¡ ¡ ¡ ¡ ¡val ¡q ¡= ¡for ¡{i ¡<-­‑ ¡Issues ¡if ¡i.productId ¡=== ¡issuePid} ¡yield ¡i.nextMd5 ¡ ¡ ¡ ¡ ¡ ¡ ¡q.update(nextMd5) ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡def ¡updateIssueInstalledMd5(issuePid: ¡String, ¡installedMd5: ¡String): ¡DB[Unit] ¡= ¡{ ¡ ¡ ¡ ¡ ¡DB ¡{ ¡implicit ¡s ¡=> ¡ ¡ ¡ ¡ ¡ ¡ ¡val ¡q ¡= ¡for ¡{i ¡<-­‑ ¡Issues ¡if ¡i.productId ¡=== ¡issuePid} ¡yield ¡i.installedMd5 ¡ ¡ ¡ ¡ ¡ ¡ ¡q.update(Some(installedMd5)) ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡} ¡ }

slide-53
SLIDE 53

The Bad

slide-54
SLIDE 54

SBT - The Promise

// ¡build.sbt ¡ name ¡:= ¡"hello" ¡ version ¡:= ¡"1.0" ¡ scalaVersion ¡:= ¡“2.10.3" ¡

  • // ¡Hi.scala ¡
  • bject ¡Hi ¡{ ¡

¡ ¡def ¡main(args: ¡Array[String]) ¡= ¡println("Hi!") ¡ }

slide-55
SLIDE 55

SBT - The Reality

  • bject ¡OomphAndroidBuild ¡extends ¡Build ¡{ ¡

¡ ¡lazy ¡val ¡baseSettings ¡= ¡Defaults.defaultSettings ¡++ ¡org.scalastyle.sbt.ScalastylePlugin.Settings ¡++ ¡Seq( ¡ ¡ ¡ ¡ ¡scalaVersion ¡:= ¡"2.10.1", ¡ ¡ ¡ ¡ ¡version ¡:= ¡"1.0.4", ¡ ¡ ¡ ¡ ¡versionCode ¡:= ¡5, ¡ ¡ ¡ ¡ ¡platformName ¡in ¡Android ¡:= ¡"android-­‑17", ¡ ¡ ¡ ¡ ¡buildToolsVersion ¡in ¡Android ¡:= ¡"18.1.1", ¡ ¡ ¡ ¡ ¡scalacOptions ¡++= ¡Seq(...), ¡ ¡ ¡ ¡ ¡commands ¡++= ¡Seq(Command.command("ci", ¡Help.empty)(s ¡=> ¡Seq("...") ¡::: ¡s)) ¡ ¡ ¡) ¡++ ¡addCommandAlias("compile-­‑and-­‑check", ¡";app/compile;app/scalastyle") ¡ ¡ ¡ ¡ ¡val ¡signingSettings ¡= ¡Seq( ¡ ¡ ¡ ¡ ¡keyalias ¡in ¡Android ¡:= ¡"oomph", ¡ ¡ ¡ ¡ ¡cachePasswords ¡in ¡Android ¡:= ¡true, ¡ ¡ ¡ ¡ ¡keystorePath ¡in ¡Android ¡<<= ¡baseDirectory(_ ¡/ ¡"etc" ¡/ ¡"keystore") ¡ ¡ ¡) ¡ ¡ ¡ ¡ ¡val ¡proguardSettings ¡= ¡... ¡ ¡ ¡ ¡ ¡lazy ¡val ¡appSettings ¡= ¡baseSettings ¡++ ¡AndroidProject.androidSettings ¡++ ¡proguardSettings ¡++ ¡ ¡ ¡ ¡ ¡ ¡ ¡TypedResources.settings ¡++ ¡AndroidManifestGenerator.settings ¡++ ¡AndroidMarketPublish.settings ¡++ ¡ ¡ ¡ ¡ ¡ ¡ ¡AndroidManifestGenerator.settings ¡++ ¡signingSettings ¡++ ¡ ¡ ¡ ¡ ¡ ¡ ¡Seq( ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡name ¡:= ¡"Oomph ¡Android", ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡installEmulator ¡in ¡Android ¡~= ¡{ ¡_ ¡=> ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡Seq("adb", ¡"-­‑e", ¡"shell", ¡"touch ¡/data/data/com.oomphhq.android/i_am_an_emulator") ¡! ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡ ¡ ¡ ¡ ¡) ¡ ¡ ¡ ¡ ¡lazy ¡val ¡testsSettings ¡= ¡baseSettings ¡++ ¡AndroidTest.androidSettings ¡++ ¡proguardSettings ¡++ ¡AndroidManifestGenerator.settings ¡++ ¡ ¡ ¡ ¡ ¡ ¡ ¡Seq( ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡name ¡:= ¡"Tests", ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡proguardInJars ¡in ¡Android ¡:= ¡Seq(), ¡// ¡wot! ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡libraryDependencies ¡++= ¡Seq(...) ¡ ¡ ¡ ¡ ¡ ¡ ¡) ¡ ¡ ¡ ¡ ¡lazy ¡val ¡app ¡= ¡Project("app", ¡file("."), ¡settings ¡= ¡appSettings) ¡ ¡ ¡ ¡ ¡lazy ¡val ¡tests ¡= ¡Project("tests", ¡file("tests"), ¡settings ¡= ¡testsSettings) ¡dependsOn ¡(app ¡% ¡"provided") ¡ }

slide-56
SLIDE 56

Little BUILD CHAIN Hacks

  • sbt plugin to fix proguard
  • Library jars not being included in binary!
  • Custom JRE (script) in IntelliJ to fix memory issues (fixed

now)

  • Double up of libraries “provided” scope [1] & [2]
  • Build tools in v19 has dex bug

[1] https://github.com/jberkel/android-plugin/pull/177 [2] https://groups.google.com/forum/?fromgroups=#!topic/scala-

  • n-android/OuGYJtvQdZo
slide-57
SLIDE 57

No Migration Support

  • Migration support not built in
  • Some Scala support (Java?)
  • You will probably need to roll your own
  • Moderately difficult to integrate Slick with

Android DB with migrations

slide-58
SLIDE 58

ToolChain

  • The toolchain described above isn’t (wasn’t?)

well supported

  • Many moving parts; IDE, Android, compiler,

plugin

  • Not as simple as iOS, normal for JVM people
  • However, plenty of support for Java…
slide-59
SLIDE 59

Learning

  • Scala has a moderately high learning curve
  • Quite a complicated language, syntactically &

conceptually

  • Lots of ways to do the same thing, e.g. _
  • 3 kinds of Scala; Java, “idiomatic”, FP
slide-60
SLIDE 60

FP

  • Functional patterns can make learning Scala

harder

  • Can be hard to fit FP concepts Android;

immutability, (lack of) subclass inheritance

slide-61
SLIDE 61

Compiler

  • Compiler bugs, much better now
  • Compiler speed, though many work arounds &

sbt’s incremental compiler quite decent

slide-62
SLIDE 62

The Ugly

slide-63
SLIDE 63

Still in Kansas

  • You may have a nicer language, but it’s still

Android

  • Stub classes when testing
  • Same device compatibility issues
  • Lack of standardisation, storage paths
  • Binary size restrictions
slide-64
SLIDE 64

Implicits

  • Deceptively simple
  • People seem to suffer horrendously with them
  • Incredibly hard to debug
  • Tip: Use them sparingly, apply some rules
  • Bijection
  • Import them close to the call site, keep scope small
slide-65
SLIDE 65

JDK8

[info] ¡Compiling ¡1 ¡Scala ¡source ¡to ¡/Users/tom/Projects/Oomph/oomph-­‑ android/project/project/target/scala-­‑2.9.2/sbt-­‑0.12/classes... ¡ [error] ¡error ¡while ¡loading ¡CharSequence, ¡class ¡file ¡'/Library/Java/ JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/jre/lib/rt.jar(java/ lang/CharSequence.class)' ¡is ¡broken ¡ [error] ¡(bad ¡constant ¡pool ¡tag ¡18 ¡at ¡byte ¡10) ¡ [error] ¡error ¡while ¡loading ¡Comparator, ¡class ¡file ¡'/Library/Java/ JavaVirtualMachines/jdk1.8.0_05.jdk/Contents/Home/jre/lib/rt.jar(java/ util/Comparator.class)' ¡is ¡broken ¡ [error] ¡(bad ¡constant ¡pool ¡tag ¡18 ¡at ¡byte ¡20) ¡ [error] ¡two ¡errors ¡found ¡ [error] ¡(compile:compile) ¡Compilation ¡failed

slide-66
SLIDE 66

JDK8

$ ¡export ¡JAVA_HOME=/Library/Java/Home ¡ $ ¡sbt

slide-67
SLIDE 67

Proguard is you’re friend (?)

  • Heavily optimise proguard to not hit (class) limits
  • Hard to know if you’ve removed too much
  • Don’t know if binary runs, classes missing!
  • Hard to exercise all code paths
  • Automated tests, QA team
  • Static analysis?
slide-68
SLIDE 68

Proguard

val ¡proguardSettings ¡= ¡Seq( ¡ ¡ ¡useProguard ¡in ¡Android ¡:= ¡true, ¡ ¡ ¡proguardOption ¡in ¡Android ¡:= ¡ ¡ ¡ ¡ ¡ ¡ ¡""" ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡|-­‑keep ¡public ¡class ¡scala.Function1 ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡|-­‑keep ¡public ¡class ¡scala.reflect.ScalaSignature ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡|-­‑keep ¡class ¡* ¡extends ¡java.util.ListResourceBundle ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡| ¡ ¡ ¡protected ¡Object[][] ¡getContents(); ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡|} ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡|-­‑keepclassmembers ¡enum ¡* ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡| ¡ ¡ ¡public ¡static ¡**[] ¡values(); ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡| ¡ ¡ ¡public ¡static ¡** ¡valueOf(java.lang.String); ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡|} ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡|-­‑keepclassmembers ¡class ¡scala.collection.** ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡| ¡ ¡ ¡*; ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡|} ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡|-­‑keep ¡public ¡class ¡scala.math.Ordering ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡|-­‑keepclassmembers ¡class ¡com.oomphhq.whiplash.** ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡| ¡ ¡ ¡*; ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡|} ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡ ¡|-­‑keep ¡public ¡class ¡scala.slick.lifted.DDL ¡{ ¡*; ¡} ¡ ¡ ¡ ¡ ¡ ¡ ¡""".stripMargin ¡ )

slide-69
SLIDE 69

Webviews

  • Well…
  • Webviews are webviews
  • Scala can’t help us here
  • Still bad support across devices
slide-70
SLIDE 70

Tips

slide-71
SLIDE 71

Learn FP

  • Use Scala as a vehicle to learn FP
  • Can share concepts; iOS (Swift), Android (Scala), Web (Elm,

TypeScript, PureScript, Roy)

  • Gradual learning curve
  • Start with Scala syntax
  • Use current programming style
  • Gradually introduce FP concepts
  • Read lots; FP in Scala, LYAH, “reactive”, etc.
slide-72
SLIDE 72

Invest in toolchain

  • The toolchain is ordinary, but
  • The toolchain is critical
  • Invest in it
  • Become build experts
slide-73
SLIDE 73

(F)RP

  • (Functional) reactive programming is all the rage
  • Promises easy composability
  • https://github.com/ReactiveX/RxJava, & on Android [1, 3]
  • Good introduction to FRP [2]

[1] http://mttkay.github.io/blog/2013/08/25/functional-reactive-programming-on-android-with- rxjava/ [2] https://gist.github.com/staltz/868e7e9bc2a7b8c1f754 [3] https://github.com/andrewhr/rxjava-android-example

// ¡send ¡button ¡enabled ¡when ¡we ¡have ¡a ¡message ¡ messageBodyText.map ¡{!_.trim().equals("") ¡}. ¡ ¡ ¡ ¡ ¡subscribe(Properties.enabledFrom(message));

slide-74
SLIDE 74

Roboelectric

  • Run tests locally rather than on device/emulator
  • Implementations of Android SDK
  • No mocking
  • Java-based API
  • JUnit runner

https://github.com/robolectric/robolectric

slide-75
SLIDE 75

Robospecs

  • Roboelectric for Specs2

https://github.com/jbrechtel/robospecs

class ¡MainActivitySpecs ¡extends ¡RoboSpecs ¡with ¡Mockito ¡{ ¡ ¡ ¡"clicking ¡the ¡showMessageButton" ¡should ¡{ ¡ ¡ ¡ ¡ ¡"show ¡a ¡toast ¡popup ¡with ¡text ¡from ¡the ¡message ¡input ¡field" ¡in ¡{ ¡ ¡ ¡ ¡ ¡ ¡ ¡val ¡activity ¡= ¡new ¡MainActivity() ¡ ¡ ¡ ¡ ¡ ¡ ¡activity.onCreate(null) ¡ ¡ ¡ ¡ ¡ ¡ ¡activity.messageEditText.setText("expected ¡message") ¡ ¡ ¡ ¡ ¡ ¡ ¡activity.showMessageButton.performClick() ¡ ¡ ¡ ¡ ¡ ¡ ¡ShadowToast.getTextOfLatestToast ¡must ¡beEqualTo(“...”) ¡ ¡ ¡ ¡ ¡} ¡ ¡ ¡} ¡ }

slide-76
SLIDE 76

Misc…

  • Can pre-load Scala onto rooted devices [1]
  • Android SBT plugin is built into IntelliJ 14 [2, 3]

[1] https://github.com/jbrechtel/Android-Scala-Installer [2] https://github.com/JetBrains/sbt-structure/pull/5 [3] http://youtrack.jetbrains.com/issue/SCL-6273

slide-77
SLIDE 77

Fork

  • Paul Phillips https://github.com/paulp/policy/blob/

master/README.md

  • Comments https://news.ycombinator.com/item?

id=8276565

  • TypeLevel http://typelevel.org/blog/2014/09/02/

typelevel-scala.html

  • Watch Paul’s talk: https://www.youtube.com/watch?

v=TS1lpKBMkgg

slide-78
SLIDE 78

References

  • Scala my Android: http://ktoso.github.io/scala-

android-presentation/

slide-79
SLIDE 79

Tom Adams
 @tomjadams