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,167