Fork me on GitHub

Wednesday, April 24, 2013

Accessing widgets in view classes

In conventional Android programming, UI widgets are accessed by using findViewById() method as shown below:
var name: EditText = null

override def onCreate(b: Bundle) {
  super.onCreate(b)
  setContentView(R.layout.main)

  name = findViewById(R.id.name).asInstanceOf[EditText]
}

def printName() {
  println("The name is " + name.getText())
}
This causes some major problems:

  • Not type-safe
    You have to manually track the type of the widget, and no compile-time guarantee that you did not made any mistake.
  • The member name is mutable
    It is better to declare name as immutable because it refers only one widget.
  • Possibility of NullPointerException
    Accessing name before calling onCreate() summons NPE-the-horrible!
  • Requires extra XML configurations to maintain
    Use XML layout if you like housekeeping chores.

  • In Scaloid, you can rewrite it with:
    var name: SEditText = null
    
    onCreate {
      contentView = SVerticalLayout {
        name = SEditText("Default name")
      }
    }
    
    def printName() {
      println("The name is " + name.text)
    }
    
    This looks much better, because it is type-safe and eliminates XML configurations. However, the member name is still a mutable variable. It can be improved as shown below:
    lazy val name = new SEditText("Default name")
    
    onCreate {
      contentView = SVerticalLayout {
        this += name
      }
    }
    
    def printName() {
      println("The name is " + name.text)
    }
    
    Now the member name is a lazy value, therefore the possiblilty of NPE is eliminated as well.

    8 comments:

    1. doesn't that make the view definition a bit too fragmented? I do like the typesafe approach of not having to use findViewById, but a good thing of the XML is that it kind of separate the view from the controller.
      Just wanted to know how you were dealing with this?

      ReplyDelete
    2. Because Scaloid layout description is an ordinary Scala code, you can handle the complexity of your code. Subclassing, superclassing, traits, factory method... or anything you wish.

      However, most of the time you don't need to separate layout description with the view class, because it is just simple. You may think that layout description need to be separated because XML layout is too verbose. It is a matter of two verbose things(XML + Java) versus one simple thing(Scaloid).

      ReplyDelete
    3. How would you handle having two layouts? One for say tablets and one of phones?

      ReplyDelete
    4. def isTablet {...}
      def createTabletLayout {...}
      def createPhoneLayout {...}

      contentView = if(isTablet) createTabletLayout else createPhoneLayout

      ReplyDelete
    5. Hm, but a real project has many xml resources for different mobile devices. I think, we'll write too much scala code for it.
      And google standardized resource folders.
      For best practices we will need create similar folder for our scala resources.
      Right? I need more examples, of course

      ReplyDelete
    6. You can use find(R.id.myresorce) anytime you wish. You don't need to migrate entire xml to Scaloid style at once.

      If you afraid of effectiveness of the xml-free approach. Start with migrating the most simple layout from your project. You will find that the "real" project need not have multiple xml files that much. If you design your layout responsive (similar to "responsive web"), single or two layout description would be sufficient for the most time.

      I wrote a xml-free android app that has downloaded more than 40,000 times.

      ReplyDelete
    7. Have you come up with a standardized way to handle internationalization of text? I believe one of the benefits of defining strings in resource files is you can drop in different files for different languages as needed. I know the short term fix would be to simply keep defining strings in resource files and just referencing them in the Scala code, but is there a better solution?

      ReplyDelete
    8. To be more specific, I meant xml layout description when I said xml-free.
      Internationalized string resources can easily be referenced from scala code. In Scaloid, resource IDs are implicitly converted into correspondinig data types:

      https://github.com/pocorall/scaloid#resource-ids

      ReplyDelete