Scala on Android - Layout and Styles

Scala on Android - Layout and Styles

Previously in our Scala on Android Series, we discussed how to prepare the environment and setting SBT configuration. This time we’ll review how this works with Layouts and our proposal that uses functional programming inside the UI.

For our purposes, we made a project on GitHub to show you the different steps for starting in Scala on Android. We’ve included an SBT groll plugin that you can use to navigate commits and learn step-by-step using:

> groll next

The app is simple. It shows different users in a list with their name and age. We’ve preloaded the list with several default users and used random.org to create random users from the Internet for us.

Let’s look at the piece of code that’s working with layouts in this project.

#Plain old Android way - Imperative Style Layouts and styles can be created in the same manner as Android using Java, but now you’re able to utilize the additional advantages of Scala.

Our project uses a RecyclerView to show different users. We’ve created an XML Layout like this:

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

	<TextView
        android:id="@+id/message"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

	<android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

	<TextView
        android:id="@+id/downloading"
        android:text="@string/downloading"
        android:layout_gravity="bottom"
        android:padding="@dimen/padding_default"
        android:textColor="@color/text_loading"
        android:background="@color/background_loading"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</FrameLayout>

The FrameLayout contains different widgets for showing the information. A RecyclerView is utilized for loading information, a TextView for showing an error message, if it’s necessary, and another TextView aligned at the bottom for showing a loading message when downloading information from the Internet.

This next piece of code is where we load the XML Layout in our activity. I’m sure that if you’ve worked in Android before, this code will look very familiar:

class MainActivity
  extends AppCompatActivity
  with «TypedFindView»
  with ComponentRegistryImpl {
	lazy val recyclerView = findView(TR.recyclerview)
	lazy val message = findView(TR.message)
	lazy val downloading = findView(TR.downloading)

	override def onCreate(bundle: Bundle) {
		super.onCreate(bundle)
		setContentView(R.layout.main)
		recyclerView.setHasFixedSize(true)
		recyclerView.setAdapter(UserAdapter(users))
		recyclerView.setLayoutManager(new LinearLayoutManager(this))
	}
}
»SBT Android plugin trait|Help to find views in our main layout«

Previously, we talked about the android-sdk-plugin, which can offer some additional helpful features here. First off is the trait TypedFindView. The plugin generates a typed resource called TR (similar to R class in Android), and you can use it in the method findView to avoid casting the variables. Moreover, Scala provided lazy vals that allows you to remove ButterKnife (if you use it) from your project.

For this particular case, we’re using the Cake Pattern for dependency injection (you can learn more about Cake Pattern in Jonas Bonér’s post. Our ApiService gets two calls, a random string for name and a random integer for age and returns a User object.

Another interesting and relevant piece of code is as follows:

override def addUser: Future[User] = for {
	Some(name) <- apiService.readName(getString(R.string.api_read_name))
	Some(age) <- apiService.readAge(getString(R.string.api_read_age))
} yield User(name, age)

We are using a for comprehension to work with several async tasks and you can use this service from your activity:

private[this] def addItem() = {
	downloading.setVisibility(View.VISIBLE)
	userService.addUser map {
		user =>
			«runOnUiThread»(new Runnable {
				override def run(): Unit = addUserToList(user)
			})
	} recover {
		case _ => showErrorMessage()
	}
}
»From Android SDK|Runs the specified action on the UI thread«

You have to use runOnUiThread method to ensure that you are working on the UI Thread. There are better options for doing this, but we’ll learn those later.

We have learned to create our UI and use Futures in async tasks to transform our views.

This is the method used in Imperative Programming. In the next section, we’ll learn how we can work using functional programming. Are you ready?

Functional way. Programming your UI from functional a perspective

We love functional programming, so why are we not using it in our UI? We’ve previously talked about Macroid, and according to Macroid’s website: “Macroid is the most badass modular functional user interface creation language for Android, implemented with Scala macros.” It’s also amazing being able to create interfaces using a functional way. Why?

  • You can compose your UI Actions and launch them when you need it
  • You have a simple DSL to work with your widgets
  • You can work with animations easily
  • You can use pattern matching to find views in your layouts

RecyclerView example using Macroid

We’re going to give you a similar example as above, but now using Macroid. For that, we’re going to use the Scala Days Official App (you can download from Google Play or if you prefer to compile the code, cloning the project from GitHub).

If you have already downloaded the project, you may have noticed something weird. The project doesn’t have XML files for layouts and styles. Macroid creates the UI using macros in a really simple way. It’s the same, but they are not expressed via the Android XML traditional layout files. In Scala Days, we are using lists on different screens like Schedule, Speakers, Social, and Sponsors. We have a trait called ListLayout where we create these lists.

trait ListLayout {
	var recyclerView = slot[RecyclerView]
	var progressBar = slot[ProgressBar]

	def content(implicit context: ActivityContextWrapper) = getUi(
		l[FrameLayout](
			w[ProgressBar] <~ «wire»(progressBar) <~ progressBarStyle,
			w[RecyclerView] <~ wire(recyclerView) <~ recyclerViewStyle
		) <~ rootStyle
	)
}
»Macroid|Connect your view to a variable«

You only have to use the getUi method and add your widgets and layouts. You have to use l for ViewGroups (e.g l[FrameLayout]) and w for your widgets (e.g w[TextView] or w[ImageView])

But, what’s <~? Easy. You can change some properties of your widget using this symbol and it’s called Tweaks. For example, you can use w[TextView] <~ text("Hello!") to change the text to your TextView. We always use a trait for all styles of our views. You can see this in the code progressBarStyle or recyclerViewStyle, these methods are in ListStyles trait

trait ListStyles {
	val recyclerViewStyle: Tweak[RecyclerView] = «vMatchParent» + «rvNoFixedSize»
	val progressBarStyle: Tweak[ProgressBar] = «vWrapContent» + «flLayoutGravity»(Gravity.CENTER)
}
»Macroid-Extras|Width and height match parent«
»Macroid-Extras|Property for RecyclerView«
»Macroid-Extras|Width and height wrap content«
»Macroid-Extras|Property for FrameLayout«

Macroid has a few tweaks created but you can use macroid-extras library, where we have created a bunch of tweaks for you to use in your views.

Ok, this all seems nice, but it’s possible that you’re wondering about wire in the code. Do you remember findViewByIdd? You can connect a view with a field in your class, and you can use it later, but there is a really important thing to note: slot is an Option[Widget] with a value of None. Your code will be better and safer. Why? When you apply your UI Action in your views, if the view wasn’t created or was destroyed, the UI Action will never be launched. Goodbye, NullPointersExceptions.

Finally, we’re going to learn how to insert our new layout in the activity or fragment and change it later. For that, we have selected the Speaker Screen in the Scala Days App because it’s simpler than others. You can see the code in SpeakersFragment:

class SpeakersFragment
	extends Fragment
	with Contexts[Fragment]
	with ListLayout {

override def onCreateView(i: LayoutInflater, c: ViewGroup, a: Bundle): View = «content»

override def onViewCreated(view: View, savedInstanceState: Bundle): Unit = {
	super.onViewCreated(view, savedInstanceState)
	runUi(
		(recyclerView
			<~ rvLayoutManager(new LinearLayoutManager(context))) ~
		loadSpeakers() ~
		(reloadButton <~ «On.click»(
			loadSpeakers(forceDownload = true)
		)))
}
»ListLayout trait|Method defined in ListLayout«
»Macroid|UI Action clicking button«

You can see that onCreateView returns the previous content method (remember that we’ve included the ListLayout trait). In an Activity, we’ll use setContentView(content).

In onViewCreated you can see how we are transforming the views using runUi. With this method of Macroid, we are forcing your UI Actions in the Android UI Thread. You can combine it in Future for ensure that you are making your changes in the right thread. You also can use the ~ symbol for combining different widgets or UI Action in the runUi method.

This is good so far, but we want to let you in on some more amazing things :-)

Animations

In our discussion about animations, we’re going to show you another project called Scala API Demos (you can download from Google Play or clone from GitHub). The project has several examples using Scala on Android; each example lists the level of difficulty with both Scala and Android.

In order to explain animations, we’re going to show you the code of the example Ripple Background. You can see the animation in the next image:

Ripple Background Example

When you click on the buttons, 3 animations are produced:

  1. The circle moves to the center of rectangle
  2. Ripple animation discovers the new color
  3. Fade for making the circle appear in the bottom

If you have worked with animations in Android, it’s possible that you think that you have to concat the 3 animations with AnimatorListenerAdapter. The code in Scala using Macroid is the next thing we’ll look at:

((circleView <~~ move(rippleBackground)) ~~ (rippleBackground <~~ ripple(rippleData)) ~~ (circleView <~~ fadeIn(1000))

Magic! Maybe, or maybe not. In Macroid, the animations are called Snails, and you have to use the <~~ symbol for that. For example circleView <~~ fadeIn(1000) creates a new fade-in for the circle selected during one second. You can use the ~~ operator to concat different animations. We are going to remember the three animations:

  1. The circle moves to center of rectangle: circleView <~~ move(rippleBackground)
  2. Ripple animation discovers the new color: rippleBackground <~~ ripple(rippleData)
  3. Fade for making the circle appear in the bottom: circleView <~~ fadeIn(1000)

The left side is the view where we are going to apply the animation (remember slot[Widget]), but what’s the right side? Simple, the right side is the Snail. You can create your own snails and use it in different views in your app. The next code is the fadeIn method used in the 3rd animation:

def «anim»(animation: Animation, duration: Long = -1L) = Snail[View] { x ⇒
»Snail method|General snail for animate view«
	val animPromise = Promise[Unit]()
	animation.setAnimationListener(new AnimationListenerAdapter {
		override def onAnimationEnd(a: Animation) { animPromise.complete(Success(())) }
	})
	if (duration >= 0) animation.setDuration(duration)
	x.startAnimation(animation)
	animPromise.future
}

def «fadeIn»(millis: Long) = show ++ anim(new AlphaAnimation(0, 1), duration = millis)
»Fade In|Snail and Tweak composition«

So no, it’s not magic. We should create the animations using Android SDK, but you can compose your animations easily in different parts of your application.

The anim method is the Snail. You only have to create a Promise like the example and call success when the animation is finished. The magic is the responsibility of Macroid.

The fadeIn method has an interesting function allowing you to combine tweaks and snails. In this example, when you add fadeIn to your code, we first show the view (like view.setVisibility(VISIBLE)) and then create the fade-in animation later. For that, we can use ++ operator for snails and tweak composition.

Conclusion

Scala is amazing for creating a new DSL for your UIs, letting you simplify your code and reuse it in your app. Not only will your code be elegant, removing the listeners will make it easier to understand.

If you want to try it, we invite you to create a new example in the Scala API Demos project. Send us a PR and we’d be happy to help you along the way!

blog comments powered by Disqus

Ensure the success of your project

47 Degrees can work with you to help manage the risks of technology evolution, develop a team of top-tier engaged developers, improve productivity, lower maintenance cost, increase hardware utilization, and improve product quality; all while using the best technologies.