I have a Java Android application which I want to change to Scala. I have many fragments and I want to know what is the best way to do this in Scala.
This is my Java fragment class MyFragment:
public class MyFragment extends Fragment {
private WebView myWebView;
private TextView myTextView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View myView = inflater.inflate(R.layout.my_view, container, false);
myWebView = (WebView) myView.findViewById(R.id.my_webview);
myWebView.loadUrl("http://www.google.com/");
myTextView = (TextView) myView.findViewById(R.id.my_textview);
myTextView.setText("Google.com");
return myView;
}
}
I always have this base structure: some private UI elements which I instantiate in onCreateView, do some things and return the view (Not shown here: in other on* methods I also do some actions with the UI elements).
I found some articles which do a lazy val like described here: http://blog.andresteingress.com/2011/09/20/programming-android-with-scala/
But in my case, this does not work, because I have fragments and not activities. First I have to inflate the main View myView and then I can get the UI elements of it.
What is the best way to do this in Scala?
--- UPDATE ---
My Scala code looks like this at the moment:
class MyFragment extends Fragment {
private var myTextView: TextView = null
override def onCreateView(inflater: LayoutInflater,
container: ViewGroup, savedInstanceState: Bundle): View = {
val myView = inflater.inflate(R.layout.my_view, container, false)
val myWebView = myView.findViewById(R.id.my_webview).asInstanceOf[WebView]
myWebView.loadUrl("http://www.google.com/")
myTextView = myView.findViewById(R.id.my_textview).asInstanceOf[TextView]
myTextView.setText("Google.com")
myView
}
}
So, what can I improve here? myTextView is a private var because I have to access it several methods in this Fragment. It seems I can not do the stuff explained here: http://blog.andresteingress.com/2011/09/20/programming-android-with-scala/ with lazy val TypedActivity and the implicit conversion of OnClickListener, because I use fragments. So how can I get rid of boilerplate code with .asInstanceOf[T] and make it more Scala like?
Based on your updated code I can only make some suggestion to be more "scala-ish"
Use Option instead of null for your members
private var myWebView: Option[WebView] = None
private var myTextView: Option[TextView] = None
To avoid explicit casting of your views in the code, you need to move it elsewhere, but you cant' get rid of it, because the original android API doesn't give you any clue as to the runtime or compiletime type of the returned objects. To overcome this issue, the post you mentioned uses custom-made typed resources and a trait that handles the types from these.
case class TypedResource[T](id: Int)
object TR {
object id {
val my_webview = TypedResource[TextView](R.id.my_webview)
val my_textview = TypedResource[WebView](R.id.my_textview)
//... you must put here all your typed views referenced by id
}
}
trait TypedViewHolder {
def view: View
//the method explicitly casts to the needed resource type based on the argument
def findView[T](tr: TypedResource[T]): T = view.findViewById(tr.id).asInstanceOf[T]
}
object TypedResource {
//this will implicitly convert your views to a corresponding TypedViewHolder
//this lets you avoid explicit type cast to get your view
implicit def view2typed(v: View): TypedViewHolder = new TypedViewHolder { def view = v }
}
Now we can use the above code
val myView = inflater.inflate(R.layout.my_view, container, false)
val myWebView = myView.findView(TR.id.my_webview)
myWebView.loadUrl("http://www.google.com/")
val myTextView = myView.findView(TR.id.my_textview)
myTextView.setText("Google.com")
Putting both things together
class MyFragment extends Fragment {
private var myWebView: Option[WebView] = None
private var myTextView: Option[TextView] = None
override def onCreateView(
inflater: LayoutInflater,
container: ViewGroup,
savedInstanceState: Bundle): View = {
//imports the implicit conversion
import TypedResource._
val myView = inflater.inflate(R.layout.my_view, container, false)
myWebView = Some(myView.findView(TR.id.my_webview))
//now we're using options, so we must call methods on the inner value
//we can use Option.map(...) to do it [see http://www.scala-lang.org/api/current/index.html#scala.Option]
myWebView.map(_.loadUrl("http://www.google.com/"))
myTextView = Some(myView.findView(TR.id.my_textview))
//same as above
myTextView.map(_.setText("Google.com"))
myView
}
}
I hope this helps you out. I'm no expert with android so I can only get so far.
Why don't you simply write the fragment in Scala without bothering about "the best way to do this in Scala"? There might be none.
I'd start with removing public from the class definition and including the other goodies - TypedActivity from the article - in the activity. Then, set up the development environment - the IDE - and run the application. If it works, you're done (with the very first step in the migration). I don't think you need lazy val's from the very beginning.
Do small steps so the migration's easier.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With