Android + Scala

Gregg Hernandez <gregg@lucidchart.com>

golucid.co

Lucid's Android Story

Google IO 2017

Why Scala?

Neat Features

  • case class
  • lazy val
  • ad-hoc polymorphism
  • metaprogramming

case class

class Thing {

  private String name;
  private int count;

  Thing(String name, int count) {
    this.name = name;
    this.count = count;
  }

  String getName() {
    return this.name;
  }

  int getCount() {
    return this.count;
  }

  @Override
  String toString() {
    return "Thing(" + this.name + "," + this.count.toString + ")";
  }

  @Override
  boolean equals(Object that) {
    if (this == that) return true;

    if (!(that isInstanceOf Thing)) return false;

    Thing otherThing = (Thing)that;

    return otherThing.name == this.name &&
      otherThing.name == this.count;
  }

  @Override
  int hashCode() {
    ...
  }

  

  

case class

case class Thing(name: String, count: Int)

  

lazy val

class MainActivity extends Activity {
  
  private SharedPreferences preferences = null;

  @Override
  void onCreate(Bundle state) {
    super.onCreate(state);

    preferences = getSharedPreferences("prefs", Context.MODE_PRIVATE);
  }

  @Override
  void onActivityResult(int request, int result, Intent data) {
    SharedPreferences.Editor editor = preferences.edit();
    editor.putString("last_used", System.getCurrentTimeMillis());
    editor.apply();

    ...
  }

}

lazy val

class MainActivity extends Activity {
  
  private lazy val preferences = 
    getSharedPreferences("prefs", Context.MODE_PRIVATE)

  override
  def onActivityResult(request: Int, result: Int, data: Intent) {
    SharedPreferences.Editor editor = preferences.edit()
    editor.putString("last_used", System.getCurrentTimeMillis())
    editor.apply()

    ...
  }

}

ad-hoc polymorphism

JSONObject json = new JSONObject("{...}");

json.getString("key");
json.getInt("count");
json.getBoolean("yes");

ad-hoc polymorphism

JSONObject json = new JSONObject("{...}");

json.getUrl("url"); // doesn't work!

ad-hoc polymorphism

JSONObject json = new JSONObject("{...}");

new URL(json.getString("url"));

ad-hoc polymorphism

val json = new JSONObject("{...}")

json.get[URL]("url")
json.get[Int]("count")
json.get[WhateverYouWant]("hi")

ad-hoc polymorphism

// JSONObject
def get[A: JsonReader](key: String) = {
  // omitted
}

// elsewhere
abstract class JsonReader[A] {
  def parse(json: JSONObject, key: String): A
}

ad-hoc polymorphism

implicit val urlJsonReader = new JsonReader[URL] {
  def parse(json: JSONObject, key: String) = { 
    /* code that parases a url from json at key */
  }
}

implicit val whateverJsonReader = 
  new JsonReader[WhateverYouWant] { ... }

ad-hoc polymorphism

val json = new JSONObject("{...}")

json.get[URL]("url")
json.get[Int]("count")
json.get[WhateverYouWant]("hi")

metaprogramming

def generateJsonReader[A](): JsonReader[A] = macro ???

metaprogramming

def generateJsonReader[A](): JsonReader[A] = macro ???

case class Thing(a: String, b: Int, c: URL)

implicit val thingReader = generateJsonReader[Thing]()

metaprogramming

def generateJsonReader[A](): JsonReader[A] = macro ???

case class Thing(a: String, b: Int, c: URL)

implicit val thingReader = new JsonReader[Thing] {
  def parse(json: JSONObject, key: String): Thing = {
    // parsing code
  }
}

sbt-android

sbt-android

  • Typed resources
  • Sbt based android build
  • Robust Android support
  • Fast builds

sbt-android-gradle

  • Use sbt and Gradle builds side by side
  • Transition to sbt

TypedResource

(ImageButton) findViewById(R.id.sendButton)

TypedResource

findView(TR.sendButton)

TypedViewHolder

<LinearLayout>
     <TextView android:id="@+id/text" />
     <ImageView android:id="@+id/image" />
</LinearLayout>

TypedViewHolder

override def onCreate(state: Bundle): Unit = {
  super.onCreate(state)

  val views = 
    TypedViewHolder.setContentView(this, TR.layout.main)

  views.text.addTextchangedlistener(???)

  views.image.setImageDrawable(TR.drawable.image.value)

}

TypedViewHolder

lazy val views = 
    TypedViewHolder.setContentView(this, TR.layout.main)

override def onCreate(state: Bundle): Unit = {
  super.onCreate()

  views.text.addTextchangedlistener(???)

  views.image.setImageDrawable(TR.drawable.image.value)

}

override def onResume(): Unit = {
  super.onResume()

  views.image.setImageDrawable(TR.drawable.whatever.value)
}

 Better Concurrency

Simple API

def longRunningOperation(): Long = ???

Runnable

new Thread(new Runnable() {
  public void run() {
    longRunningOperation();
  }
}).start();

AsyncTask

class LongRunningTask extends AsyncTask<In, Integer, Out> {
  protected Long doInBackground(In... ins) {
    return longRunningTask();
  }

  protected void onPostExecute(Out result) {
    showDialog("Got " + result);
  }
}

Future

val result = Future {
  longRunningOperation();
}

Future

val result = Future {
  longRunningOperation();
}

def double(x: Long): Long = x * 2

val doubleResult: Future[Long] = result.map(double)

Future

val result = Future {
  longRunningOperation();
}

def takesForever(x: Long): Future[String] = ???

val doubleResult: Future[Long] = 
  result.flatMap(takesForever)

Future

val result = for {
  a <- firstLongRunner()
  b <- secondLongRunnner(a)
  c <- thirdLongRunner(b)
  // etc
} yield (c * 2)

Future

val result: Future[A] = ???

result.onComplete { maybeValue =>
  // always runs when Future is done
}

result.onSuccess { value =>
  // runs when ever no exceptions are thrown
}

result.onFailure { e =>
  // runs when exceptions are thrown
}

Go learn more

sbt-android 

github.com/scala-android/sbt-android

 

Neophytes Guide To Scala

danielwestheide.com/scala/neophytes.html

Thanks

@gregghz

gregg@lucidchart.com

github.com/gregghz

golucid.co

Android + Scala

By Gregg H

Android + Scala

  • 1,032