Android-Programmierung mit Scala

Träge Variablen

Ein weiteres Merkmal der Scala-Programmiersprache ist die Einbettung von Design Patterns. Ein Beispiel ist das Schlüsselwort lazy. Wird es auf eine Instanzvariable angewendet, wird zur Laufzeit der Ausdruck auf der rechten Seite der Zuweisung nur beim ersten Zugriff ausgeführt und zwischengespeichert. Wird auf die Instanzvariable ein weiteres Mal zugegriffen, wird der Ausdruck nicht neu ausgewertet, sondern auf das Ergebnis des vorhergehenden Aufrufs zugegriffen. Dieser Mechanismus kann nun verwendet werden, um den Zugriff auf Referenzen der UI Widgets verspätet beim ersten Zugriff zu initialisieren.

Listing 4
class LoginActivity extends Activity with TypedActivity {
 
   lazy val usernameEditText = findView(TR.usernameEditText)
   lazy val passwordEditText = findView(TR.passwordEditText)
 
   lazy val loginButton = findView(TR.loginButton)
 
   override def onCreate(savedState: Bundle) {
      super.onCreate(savedState)
      setContentView(R.layout.login)
   }
}

Die Anwendung von lazy hat in Listing 4 die Auswirkung, bei einem Zugriff auf die Instanzvariable loginButton diese Variable über die Methode findView zu initialisieren. Durch die Angabe von val anstatt var werden die Variablen als final deklariert und dürfen somit nicht erneut initialisiert werden. Die Angabe von lazy verdünnt den Glue Code in der onCreate-Methode, da Referenzen auf die UI Widgets nun als Instanzvariablen vorhanden sind, die nur bei Bedarf initialisiert werden. Der nächste Schritt ist die Initialisierung des loginButton mit dem OnClickListener. Dazu verwenden wir zwei weitere Scala-Konzepte: Funktionen und implizite Typkonversionen.

Implizite Typkonversionen

Neben seiner objektorientierten Ausrichtung bietet Scala auch Elemente funktionaler Programmiersprachen. Grundlegendes Merkmal dieser Programmiersprachen ist der besondere Stellenwert der Funktion – man spricht von Funktionen als First-class-Objekten. Das bedeutet, dass Funktionen im objektorientierten Sinn ebenfalls Instanzen von Typen – den Funktionstypen – sind und auch als solche behandelt werden können. Dadurch kann der Typ einer Variablen auch Funktionstyp sein. In Listing 5 ist die Deklaration einer Variablen f zu sehen, die nur Funktionsobjekten zugewiesen werden kann, die einen Eingangsparameter vom Typ Int annehmen und als Resultat ein Objekt des Typs Int zurückliefern.

Listing 5
scala> var f: (Int => Int) = (x: Int) => x * 2
f: Int => Int = 

scala> f(2)
res0: Int = 4

Funktionstypen werden in Scala an verschiedensten Stellen in der Klassenbibliothek verwendet, wobei Paradebeispiele vor allem bei den Klassen des collection Packages zu finden sind.

Am Beispiel der LoginActivity wollen wir nun anstatt der Deklaration der anonymen OnClickListener-Klasse (mit einer einzigen Methode) ein Funktionsobjekt verwenden. Da die setOnClickListener-Methode der Button-Klasse jedoch ein spezifisches Objekt vom Typ OnClickListener benötigt, muss eine implizite Typkonversion eingeführt werden. Das ist eine Funktion, markiert mit dem implicit-Schlüsselwort, die einen Eingangstyp A in einen Ausgangstyp B konvertiert. An jenen Codestellen, die der Scala-Compiler vom Typ B her nicht korrekt auflösen kann, wird nach impliziten Konversionsfunktionen in diesen Typ B gesucht, die dann – falls passend auf den Eingangstyp A – angewandt werden. In unserem Beispiel wollen wir ein Objekt vom Funktionstyp f: (View => Unit) in ein Objekt des Typs View.OnClickListener konvertieren.

Listing 6
implicit def function2ViewOnClickListener(f: View => Unit) : View.OnClickListener = {
 
   new View.OnClickListener() {
     def onClick(view: View) {
       f(view)
     }
   }
}

Die Funktion function2ViewOnClickListener muss im Geltungsbereich jener Scala-Klassen liegen, die von der Typkonversion Gebrauch machen sollen. Nachdem die implizite Typkonversion nun spezifiziert wurde, erweitern wir unsere LoginActivity um ein Funktionsobjekt anstatt der anonymen OnClickListener-Implementierung für den loginButton.

Listing 7
loginButton.setOnClickListener( (v: View) => {
       var username = usernameEditText.getEditableText.toString
       var password = passwordEditText.getEditableText.toString

       // Authentifizierung geschieht hier!
       if (authenticated)  {
         startActivity(new Intent(LoginActivity.this, classOf[HomeActivity]));
       } else {
         finish();
       }
    })

Wie in Listing 7 zu sehen, kann nun eine Instanz des Funktionsobjekts angegeben werden. Somit werden der Code und dessen Lesbarkeit um ein weiteres Stück verbessert.

Listing 8
class LoginActivity extends Activity with TypedActivity {
  lazy val usernameEditText = findView(TR.usernameEditText)
  lazy val passwordEditText = findView(TR.passwordEditText)
  lazy val loginButton = findView(TR.loginButton)

  override def onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.login)
    loginButton.setOnClickListener( (v: View) => {
      var username = usernameEditText.getEditableText.toString
      var password = passwordEditText.getEditableText.toString
      // Authentifizierung geschieht hier!
      if (authenticated)  {
        startActivity(new Intent(LoginActivity.this, classOf[HomeActivity]));
      } else {
        finish();
      }
    })
  }
}

Unter der Anwendung der lazy vals und impliziten Typkonversionen kann die in Scala implementierte Version der LoginActivity effizient und mit durchaus weniger Glue Code implementiert werden (Listing 8). Um jedoch die wahren Vorteile von Scala im Bereich der Frameworkprogrammierung wahrzunehmen, haben wir bis zu diesem Zeitpunkt einen elementaren Sprachbestandteil außer Acht gelassen: Vererbung.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.