Scala on Android – About me – Scala...



Scala on Android – About me – Scala...

0 0


agapep.github.io


On Github agapep / agapep.github.io

Scala on Android

About me

About you?

?

About this session

  • Do ask questions whenever you feel like it!
  • Scala is Simple, but Hard.
  • Some presented topics are really advanced.

Scala...

means Stairs

Scala...

  • is a Functional language
  • ... and Object Oriented language
  • ... that runs on the JVM
  • ... so, it compiles down to Bytecode
  • ... so, it can run on Android!

The basics first!

Variables vs. Values

Coding like a final nazi!
val conference = "AndroidDeveloperBackstage"
// conference = "AndroidDeveloperBackstage" // compile error val == "java final"

var age = 90
age = 2
// age = "trolololo!" // compile error String != Int
The Type was inferred. You can type it though:
val name: String = "AndroidDeveloperBackstage"
val name: String = "AndroidDeveloperBackstage"   ; // yay semicolons, GREAT!!!

From POJO...

public class Person {
  private String name;
  private String surname;

  public Person(String name, String surname) {
    this.name = name;
    this.surname = surname;
  }

  public String getName() { return name; }
  public void setName(String name) { this.name = name; }

  public String getSurname() { return surname; }
  public void setSurname(String surname) { this.surname = surname; }

  @Override
  public String toString() { /*...*/ }
}
                

... to POSO!

class Person(name: String, surname: String)
"You forgot the toString()!"

Case class

case class Person(name: String, age: Int)
  • has toString,
  • has hashCode, equals and more scala specific things...

Collections

"Find all young women and say hello!"
 List<String> girls = FluentIterable
   .from(people)
   .filter(new Predicate<Person>() {
     @Override
     public boolean apply(Person person) {
       return person != null && person.isWoman();
     }
   })
   .transform(new Function<Person, String>() {
     @Override
     public String apply(Person input) {
       return input.getFirstName() + " " + input.getLastName();
     }
   })
   .toImmutableList();

   for(String name : names)
    println(String.format("Hello %s!", name);

       // PS: DON'T call format on Android!
And that's pretty cool, for Java, actually... (Guavarocks).

Embrace Scala Collections

The same as before, reimplemented:
people.filter(_.isWoman)
people.filter(_.isWoman).map(_.name)
people.filter(_.isWoman).map(_.name).foreach(greet _)
You could write is like this:
def greet(name: String) = println("Hello " + name + "!")

people filter { _.isWoman } map { _.name } foreach { n => greet _ } 
Another trick is:
people foreach println

Scala collections == Immutable

  • Immutability rocks
  • thread-safe by design
  • no need for manual defensive copy
  • val bothSets: Set[String] = set ++ anotherSet
    and others...

Let's roll

Off to android stuff!

findById

<Button
  android:id="@+id/my_button"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="@string/my_button_text">
Java:
class MyActivity extends Activity {

  ListView comments;
  Button newComment;

  @Override
  void onCreate(Bundle bundle) {
    super.onCreate(bundle);
    comments = (ListView) findViewById(R.id.comments);
    newComment = (Button) findViewById(R.id.new_comment);
  }
}
              
What sucks here? Tip: One thing is fixable by RoboGuice.

findView + TR

TR == TypedResource.
class MyScalaActivity extends ScalaActivity {

  lazy val Comments = findView(TR.comments)
  lazy val NewComment = findView(TR.new_comment)

  // with Types!
}
What's a lazy val?

Lazy values

var thing = 0

lazy val incrementInLazyVal = { thing += 1; thing }

thing = 100
incrementInLazyVal
incrementInLazyVal
incrementInLazyVal

assert { thing == 101 && incrementInLazyVal == 101 }
            
vs. val:
var thing = 0

val incrementInVal = { thing += 1; thing }

thing = 100
incrementInVal
incrementInVal
incrementInVal

assert { thing == 1 && incrementInVal == 1 }
Note: No one writes such convoluted Scala! But this explains when what is evaluated.

Calling methods without ()

var thing = 0

def incrementDef() = { thing += 1; thing }

incrementDef()
incrementDef
incrementDef

assert { thing == 3 }
doesn't work the other way:
def something = 1337

something // OK
something() // compile error
Intuition is: "() means side-effects"

Traits

A trait:
  • is "An interface with implementation".
  • can be "mixed in"
  • MANY traits can be mixed into one single class
  • it's NOT "just multiple inheritance", it's smarter:
  • "type linearization" avoids the "diamond problem"

Traits

trait SayVerb {
  // some magic here... Comes after the next few slides ;-)

  def sayAwesome() = "Awesome!".toast() // make a toast!
  def sayAmazing() = "Amazing!".toast()
}

trait Logging { /* logging impl */ }
class MyActivity extends Activity
  with SayVerb
  with Logging
Hey! Where did that toast method come from?!

Same Menu, Many Activities

You have to override this:
public class ActivityA extends Activity {
  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.game_menu, menu);
    return true;
  }
}
public class ActivityB extends Activity {
  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.game_menu, menu);
    return true;
  }
}
A: abstract class ActivityWithGameMenu { ... } ?
B: Just delegate the hell out of it!
C: Use a Trait

trait GameMenu

trait GameMenu extends Activity {
  override onCreateOptionsMenu(menu: Menu) = {
    getMenuInflater.inflate(R.menu.game_menu, menu)
    true
  }
}
The extends here works a bit tricky. (Yes, there are nicer ways to express this)
Usage:
class ActivityA extends Activity
  with GameMenu {
    /*...*/
}


class ActivityB extends SomethingFromAFrameworkActivity
  with GameMenu {
    /*...*/
}

Mix and match

In my small "ScalaWords" library a typical pattern is:
object Dictionary extends Logging
  with TimedVerb
  with RetryVerb
  with DoToVerb
  with UniquifyVerb
and then you use this "static singleton bag of methods" by:
def things() {
  import Dictionary._
  // use this dictionaty
}
(imagine _ works like * in Java).

Pimp my Library!

Let's pimp' Android API's!
"Hello world!".toast
So... where did that method come from? That IS a java.lang.String right? ...right?

Implicit Conversions

// Scala "magic" here
val it: java.lang.String = "Hello %s!"

it.format("Łódź")        == "Hello Łódź!"
String doesn't have the format method... but RichString has!
The mentioned "magic" is:
implicit def string2richString(s: String) = new RichString(s)

// String -> RichString

Implicit Conversions

Steps the Compiler takes: String doesn't have format(String)! Is there an Implicit Conversion (in scope), to a Type that has this method? If yes, put a call to it where the format call is needed!
"%s".format("aha!") // compiler starts looking...

// and REWRITES it to:

new RichString("%s").format("aha!")
Easy as goo pie!

Toast.makeText

Back to our Toast example

Java:

Toast.makeText(ctx, msg, Toast.LENGTH_SHORT).show()
Problems?
Where do we take the ctx (Context) from?

Multiple parameter lists

def function(a: Int)(b: Int)

Multiple parameter lists

Instead of:
def add(a: Int): (Int) => Int = addStep2(a, _)
// add returns a function that takes an Int, and returns an Int

def addStep2(a: Int, b: Int) = a + b
// the final step, adding those numbers

// usage:
val part2 = add(2)(2)
We're able to write:
def add(a: Int)(b: Int) = a + b
Tremendously useful!

Implicit Values + Parameters

Similiar to conversions, but we look for a parameter:
// IMPLICIT VALUE
implicit val ctx = getApplicationContext()

// IMPLICIT PARAMETER LIST
def example()(implicit ctx: Context) = "I have " + ctx
That's how you can use it:
                example()(getApplicationContext)

                // or!

                implicit val ctx = getApplicationContext
                example()
              

trait Toasts

trait Toasts {

  implicit def charSeq2toastable(str: java.lang.CharSequence) =
    new Toastable(str.toString)

  class Toastable(msg: String) {

    import android.widget.Toast._

    def toast(implicit ctx: Context) {
      makeText(ctx, msg, LENGTH_LONG).show()
    }
  }
}
Usage:
class MyActivity extends Activity with Toasts {
  implicit lazy val ctx = getApplicationContext

  def sayHello() { "Hello".toast }
}

Don't worry.

More pimpin' - Operators?

SomeTextView.setText("Hello World!")
Can be turned into:
SomeTextView := "Hello World!"
Altough it's just "a funny method name", NOT an "operator".
SomeTextView.:=("Hello World!")
you can use other "mych more", .
! % & * + - / : < = > ? @ \ ^ | ~

More pimpin' - onClick

Boilerplate alert!
button.setOnClickListener(new OnClickListener() {
  public void onClick(View v) {
    // do things...
  }
});
Can be replaced with:
import ListenerConversions._

button onClick { /* do things */ }
// even better:
val sendMessage = { /*...*/ }

button onClick sendMessage

Threading helpers

Instead of:
handler.post(new Runnable(){
  @Override
  void run() {
    /* ... */
  }
});
You could have meaningful methods, like:
inFuture { /* ... */ }
inUiThread { /*...*/ }

inFuture(whenComplete = notifyUser) { /* long task...*/ }

inFutureWithProgressDialog  { /* long task...*/ }

Shared preferences

Java:
SharedPreferences sp = ctx.getSharedPreferences(key, 0);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("key", "value");
editor.commit();
Compared to:
AppSharedPreferences.someKey = "value"
No implicit's here!

Funny methods - part 2

A little helper function:
def sharedPreferences(implicit ctx: Context) =
  ctx.getSharedPreferences(AppName, 0)
Read a shared preference:
def workspaceName(implicit ctx: Context) =
  Option { sharedPreferences.getString(KeyWorkspaceName, null) }
Write a shared preference:
def workspaceName_=(name: String)(implicit ctx: Context) {

  withSharedPreferencesEditor { _.putString(KeyWorkspaceName, name) }

}
Notice the method name: "something_="

What's Option?

We could map over a collection, to: apply a function for each element?
Option is like a 1 element Collection:
  • is has Some(value)
  • or None value...
Thus you can:
optionalValue map { _.toast }
Which will only be called, if the Option has Some value.

Pattern Matching and Extractors

Instead of:
if("Bob".equals(person.getName()) {
  String name = person.getName();
  String surname = person.getSurname();
  // do things...
} else {
  Log.d("You're not Bob!");
}
We can:
person match {
  case Person("Bob", surname) =>
    ("Bob's surname is: " + surname).toast
  case _ =>
    "You're not Bob!".toast
}
See how we used Person(_, _)? It's an extractor.

Roll your own Extractor

The data type:
class Data(num: Int)
It's companion object ("static methods"):
object Data { // that's called an "companion object"

  def apply(num: Int) = new Data(num)

  def unapply(i: Intent): Option[Data] =
    if (i.hasExtra("num"))
      Some(new Data(i.getIntExtra("num")))
    else
      None
}
A case class has auto-generated apply / unapply! Awesome!

Extracting our Data

Here's how to use it:
apply()
val data = Data(1)
// is the same as:
val data2 = Data.apply(2)
Remember Some(12)? Same thing.
unapply()
data match {
  case Data(num) => assert { num == 1 }  // matches here! num = 1
  case _ => ???
}
data match {
  case Data(333) => ???
  case Data(1) => // matches here!
}
The default case:
data match {
  case Something(a, b) =>
  case other => // matches here
}

Build It

Starting procedures

Install SBT (build tool) Install [Intellij IDEA] (or Android Studio) Add Scala and SBT plugins to IDEA Add Android https://github.com/pfn/android-sdk-plugin) and IDEAhttps://github.com/mpeltonen/sbt-idea) plugins to SBT Write a build file Generate IDE project files Run or In utter rage, post in the Mailing list https://groups.google.com/forum/#!forum/scala-on-android

SBT

Simple Build Tool

SBT

  • a fantastic shell
  • it's not 5000 lines of XML (hello pom.xml!) - Build.scala
  • hard to get into at first...
  • the de facto standard build tool
  • Type Safe!!!

SBT

import android.Keys._
android.Plugin.androidBuild

name := "Project-name"

version := "1.0"

platformTarget in Android := "android-19"

libraryDependencies ++= Seq(
  "com.loopj.android" % "android-async-http" % "1.4.4",
  "com.android.support" % "support-v13" % "19.1.0",
  aar("com.google.android.gms" % "play-services" % "4.0.30")
)

Sbt ~test

~ runs a command, each time a source file changes.
> ~test

Tasks: Android:[tab]

Bricks

            w[Button]
            

Bricks

            l[LinearLayout](
              w[Button],
              w[TextView]
            )
            

Bricks

            val brick1 = w[Button]
            val brick2 = w[TextView]

            l[LinearLayout](
              brick1, brick2
            )
            

Tweaks

            w[TextView] <~ text("Hello")
            

Tweaks

            def largeText(str: String) =
              text(str) +
              TextSize.large +
              padding(left = 8 dp)

            myTextView <~ largeText("Hello")
            

A complete layout

          var greeting = slot[TextView]
 
          l[LinearLayout](
            w[Button] <~
              text("Greet me!") <~
              On.click {
                greeting <~ show
              },
              
            w[TextView] <~
              text("Hello there") <~
              wire(greeting) <~ hide 
          )
          

Snails

            myTextView <~~ fadeIn(400)
            

Snails

            myTextView <~~
              fadeIn(400) <~
              text("Faded!")
            

Snails

            val blink =
              fadeIn(400) ++
              delay(2000) ++
              fadeOut(400)

            myTextView <~~ blink
            

UI actions

            val action =
              myTextView <~ text("Foo") <~ show
            

UI actions

            val action1 =
              myTextView <~ text("Foo") <~ show

            val action2 =
              myProgressBar <~ hide

            runUi(action1 ~ action2)
            

UI actions

            runUi {
              (myProgressBar <~~ fadeOut(400)) ~~
              (myTextView <~~ blink) ~~
              (myOtherTextView <~ text("’SUP?"))
            }
            

Tweaks & Signals

          // create a reactive variable
          val caption = rx.Var("Olá")

          // set text to “Olá”
          myTextView <~ caption.map(text)

          // text automatically updates to “Adeus”
          caption.update("Adeus")
          

What is wrong

proguard InteliJ idea scala backward compatibility

Proguard hell

Scala library is around 5 MB ↓ Squeeze it with ProGuard ↓ Build time goes up to 1 min / 10K LOC; ProGuard config takes some effort

I want moar!

Other links:

ありがとう!

slides are googlable Krzysztof Pecyna @ AndroidDeveloperBackstage 2014 *ale prezentacje ukradł od Konrada Malawskiego