footer: (Scala on Android) ⇒ The current state of affairs slidenumbers: true
⇒ 47 Degrees, a global consulting agency & Typesafe Consulting Partner.
@raulraja @javielinux @47deg http://47deg.com/blog
Build Tools
https://github.com/pfn/android-sdk-plugin
- Supports all Android SDK tasks
- dex
- typedResourcesGenerator
- proguard
- buildConfigGenerator
- (+ 20... more)
object TR {
val title = TypedResource[TextView](R.id.title)
object layout {
val abc_screen_toolbar = TypedLayout[ActionBarOverlayLayout](R.layout.abc_screen_toolbar)
}
}
class MyActivity extends TypedActivity {
val titleTextView = findView(title) //titleTextView inferred as TextView, no casting needed
}
https://github.com/pfn/android-sdk-plugin
- Active
- Fast (incremental compilation and proguard caching)
- Proguard + MultiDexApplication integration (Circumvents 65K method limit)
- Supports AAR, JAR and APK artifact types
- Active
- Syntax Highlighting
- Code assistance
Android Studio
: Based on IntelliJ
Google says: If you have been using Eclipse with ADT, be aware that Android Studio is now the official IDE for Android
//plain vanilla 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)
//with Scaloid
SButton("Greet", toast("Hello!"))
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="wrap_content" android:padding="20dip">
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content" android:text="Sign in"
android:layout_marginBottom="25dip" android:textSize="24.5sp"/>
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content" android:text="ID"/>
<EditText android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/userId"/>
<TextView android:layout_width="match_parent"
android:layout_height="wrap_content" android:text="Password"/>
<EditText android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/password"
android:inputType="textPassword"/>
<Button android:layout_width="match_parent"
android:layout_height="wrap_content" android:id="@+id/signin"
android:text="Sign in"/>
</LinearLayout>
//with scaloid
new SVerticalLayout {
STextView("Sign in").textSize(24.5 sp).<<.marginBottom(25 dip).>>
STextView("ID")
SEditText()
STextView("Password")
SEditText() inputType TEXT_PASSWORD
SButton("Sign in")
}.padding(20 dip)
//plain scala
new AsyncTask[String, Void, String] {
def doInBackground(params: Array[String]) = {
doAJobTakeSomeTime(params)
}
override def onPostExecute(result: String) {
alert("Done!", result)
}
}.execute("param")
//scaloid
Future {
val result = doAJobTakeSomeTime(params)
runOnUiThread(alert("Done!", result))
}
import macroid._
import macroid.FullDsl._
class GreetingActivity extends Activity with Contexts[Activity] {
override def onCreate(savedInstanceState: Bundle) = {
super.onCreate(savedInstanceState)
// the layout goes here
setContentView {
getUi {
l[LinearLayout](
w[Button],
w[TextView]
)
}
}
}
}
// ActivityContext is an Android Context obtained from an Activity
import macroid.ActivityContext
...
def layout1(implicit ctx: ActivityContext) =
l[LinearLayout](
w[TextView]
)
def layout2(implicit ctx: ActivityContext) =
l[FrameLayout](
w[ProgressBar]
)
def layout3(implicit ctx: ActivityContext) =
l[FrameLayout](
layout1,
layout2
)
...
l[LinearLayout](
// set button caption
w[Button] <~ text("Click me"),
// set text and hide for the time being
w[TextView] <~ text("Hello!") <~ hide
// set layout orientation
) <~ vertical
// AppContext is an Android Context obtained from getApplicationContext
import macroid.AppContext
// More tweaks
import macroid.contrib.TextTweaks
def greeting(greeting: String)(implicit appCtx: AppContext) =
TextTweaks.large +
text(greeting) +
hide
button <~ On.click {
...
}
// create a slot
var greeting = slot[TextView]
l[LinearLayout](
w[TextView] <~
// wire the view to the slot
wire(greeting) <~
OurTweaks.greeting("Hello!"),
w[Button] <~
text("Click me") <~
On.click {
// use the slot elsewhere
greeting <~ show
}
) <~ vertical
class MyActivity extends Activity with Contexts[Activity] {
// implicit access to AppContext & ActivityContext stored as a weak reference
}
class MyFragment extends Fragment with Contexts[Fragment] {
// implicit access to AppContext & ActivityContext stored as a weak reference
}
class MyActivity extends Activity with Contexts[Activity] {
// implicit access to AppContext & ActivityContext stored as a weak reference
}
class MyFragment extends Fragment with Contexts[Fragment] {
// implicit access to AppContext & ActivityContext stored as a weak reference
}
val focusLoudly = Snail[View] { view ⇒
view.setFocus()
playSound
}
val wink = fadeIn ++ fadeOut
editText <~ text("foo") <~~ fadeIn <~ enable
(myProgressBar <~~ fadeOut(400)) ~~
(myTextView <~~ blink) ~~
(myOtherTextView <~ text("Scala Madrid!"))
linearLayout <~ Transformer {
case t: TextView ⇒ t <~ text("foo")
case i: ImageView ⇒ i <~ hide
}
object OurTweaks {
def orient(implicit appCtx: AppContext) =
landscape ? horizontal | vertical
}
...
// in layout
l[LinearLayout](
...
) <~ OurTweaks.orient
Brings the power of Typeclasses to UI composition.
How to display A
using W
.
libraryDependencies += aar("org.macroid" %% "macroid-viewable" % "2.0.0-M3")
trait Viewable[A, +W <: View]
import macroid.viewable.Viewable
case class User(name: String)
def userViewable(
implicit ctx: ActivityContext,
appCtx: AppContext): Viewable[User, TextView] =
Viewable[User] { user ⇒
w[TextView] <~ TextTweaks.large <~ text(user.name)
}
Handling Fragment events & messaging with Actors.
class MyActor extends FragmentActor[MyFragment] {
def receive = receiveUi andThen {
case MyMessage(x) ⇒ ...
case MyOtherMessage ⇒ withUi(fragment ⇒ Ui {
// do some cool ui stuff here
})
case FragmentActor.AttachUi(_) ⇒ ...
case FragmentActor.DetachUi(_) ⇒ ...
}
}
(source code available on March 15th)
@raulraja @javielinux @47deg http://47deg.com/blog