Starting Another Activity
OK, so one way or another we have a text field and a button on the screen. To round off this tutorial we will capture the text that has been entered and send it to a new activity.
At the risk of going in too many directions at once, I'm going to attempt to demonstrate how to do this programmatically, using the XML, and with a blend of both styles.
Respond to the Send Button
The XML way
Warning as of right now the "XML way" doesn't work! When I came across this I posted a question to the RubyMotion community and it looks like this is a known bug that's not likely to get resolved soon.
I'll go ahead and write this section as if it works but be warned: it doesn't!
According to the Button
docs, our button inherited an android:onClick
XML attribute from android.view.View
. If you're like me, you'll be alarmed to note that this is one of those rare XML attributes that doesn't have a 'Related Method'!
To use this attribute, we just specify the name of a method in the activity that we want to call like this:
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_send"
android:onClick="sendMessage" />
Then all we need to do is add the sendMessage
method to the activity:
def sendMessage(view)
end
As an easy way to test that our button is hooked up as expected, we'll have it create a toast like so:
def sendMessage(view)
message = 'Howdy'
toast = TOAST.makeText(self, message, TOAST::LENGTH_SHORT)
toast.show
end
And that's all there is to it!
The Programmatic Way
Let's pretend like XML doesn't exist and go back to our old app. It's just one file, which I've prettied up a bit since you last saw it (well . . . it's prettier):
class MainActivity < Android::App::Activity
LAYOUT = Android::Widget::LinearLayout
EDIT_TEXT = Android::Widget::EditText
BUTTON = Android::Widget::Button
TOAST = Android::Widget::Toast
attr_accessor :layout, :edit_text, :button
def onCreate(savedInstanceState)
super
create_layout
create_edit_text
create_button
self.contentView = layout
end
def create_layout
@layout = LAYOUT.new(self)
layout.orientation = LAYOUT::HORIZONTAL
end
def create_edit_text
@edit_text = EDIT_TEXT.new(self)
edit_text.hint = 'Enter Some Text!'
layout.addView edit_text
layout_params = edit_text.layoutParams
layout_params.weight = 1
end
def create_button
@button = BUTTON.new(self)
button.text = 'Send'
layout.addView button
end
end
To respond to click events programmatically, we just need to set the onClickListener
for the button. The way this works out in the wash is pretty interesting. Take a look:
def create_button
@button = BUTTON.new(self)
button.text = 'Send'
layout.addView button
button.onClickListener = self
end
def onClick(view)
message = 'Howdy'
toast = TOAST.makeText(self, message, TOAST::LENGTH_SHORT)
toast.show
end
We set self as the onClickListener
! What? Magic!
How does that work? Well, just take a look at the RubyMotion docs:
In RubyMotion, you can implement an interface in a class just by defining the required methods. There is no need to specify the name of the interface, the compiler will determine that for you automatically.
That's called duck typing ladies and gentlemen.
Basically, we're saying that the only thing we need for a class to qualify as an onClickListener
is for it to implement the onClick
method, which this activity does. Very nice.
A Blended Approach
The last approach that I'll explore is one where we still declare our UI over in the XML, but instead of attaching an onClick
directly, we'll fetch the object from the XML and work with it programmatically. From what I've seen so far, this approach is pretty common to a lot of problem solving in Android land so let's take a look.
First, we need to fetch the button from the XML. We can get views (which the button is) via the Activity
class's findViewById
method. This method just needs an identifier integer which we can access again via our resources package R
. If we give our button an id like so:
<Button android:id="@+id/my_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_send" />
then the integer can be retrieved from the id package. If we're careful to capitalize the first letters: R::Id::My_button
then we'll get back a reference to the object our XML created at compile time.
Altogether that's:
class MainActivity < Android::App::Activity
TOAST = Android::Widget::Toast
def onCreate(savedInstanceState)
super
setContentView(R::Layout::First_layout)
button = findViewById(R::Id::My_button)
button.onClickListener = self
end
def onClick(view)
message = 'Howdy'
toast = TOAST.makeText(self, message, TOAST::LENGTH_SHORT)
toast.show
end
end
Starting a New Activity
The toast thing is nice, but this tutorial wouldn't be complete if we didn't take this opportunity to learn how an Intent is used to start an activity.
Using an Intent to Start the Activity
Intents can be a little . . . intense! There's a lot going on with this class so do read the API Guide on the topic, but don't read too much (I've heard you can go crazy that way). For this lesson we'll just be using the most basic kind of intent in a very basic way: an explicit intent that starts a new activity.
The bottom line is, you can't just go willy-nilly telling activities to start. You instead tell them that one bit of software intends to start another one. It's up to the other one to decide whether or not it starts.
So let's signal our intent to fire up a new activity (which I call DisplayMessageActivity
) by creating an intent (instead of a toast, though you can leave the toast in if you like).
intent = Android::Content::Intent.new(self, DisplayMessageActivity)
All this intent needs is a little context (self
) and the name of the class you're asking it to fire-up for you.
Next we call the startActivity
method and pass in the intent. Our onClick
method now looks like this:
def onClick
intent = Android::Content::Intent.new(self, DisplayMessageActivity)
startActivity intent
end
Now, if the only thing our app had was these two lines of code and a blank DisplayMessageActivity
like this:
class DisplayMessageActivity < Android::App::Activity
def onCreate(savedInstanceState)
super
end
end
then running it will throw this error:
Java exception raised:
android.content.ActivityNotFoundException:
Unable to find explicit activity class {...};
have you declared this activity in your AndroidManifest.xml?
We don't have an AndroidManifest.xml
, instead we handle project settings via
the Rakefile (more on that in a different lesson) like so:
Motion::Project::App.setup do |app|
# app.name etc.
app.sub_activities = ['DisplayMessageActivity']
end
Right now I'm not exactly sure what all sub_activities
provides for us. The RM docs are
pretty sparse on the matter and based on this lesson from the Android trainings I'd say it's doing a fair bit of work. Hmmm . . .
Anyways, you should now be able to run your app with no error (except pushing the send button
will do nothing since that onCreate
is still empty).
Passing Data Into The Activity
For both the programmatic and XML way I'll write the new activity the same way (programmaticly) because for such a small activity, XML will just get in the way. (Even the official Android training guide avoids XML for this one.)
class DisplayMessageActivity < Android::App::Activity
TEXT_VIEW = Android::Widget::TextView
attr_accessor :text_view
def onCreate(savedInstanceState)
super
create_text_view
setContentView(text_view)
end
def create_text_view
@text_view = TEXT_VIEW.new(self)
text_view.text = 'Testing testing . . . Breadsticks'
end
end
Sidenote - See all that stuff that the task in our Rakefile did for us? At the top of the screen (the
ActionBar
) we have a back button that tells us which activity we're in. Without Ruby Motion's help here, a little thing like that back button would take quite a bit more work.
In addition to starting activities, Intents live and breathe to pass data onto the next
activity. If you take a look at the Intent docs, you'll see lots of methods for putting
stuff into the Intent (methods starting with put
) and lots of things for getting
stuff out of the Intent (methods starting with get
).
For a simple string like this, we'll use an Extra
as our data structure (sorta like a Ruby hash)
like this:
intent.putExtra('key', 'value')
conveniently, a method available to DisplayMessageActivity
is getIntent
which we
can use as intent
. So in the new activity we get the string like this:
value = intent.getStringExtra('key')
Note At the time of this writing, the intent doesn't support stripping off the get
in favor of a stringExtra
method.
A Little Sweat
OK, so here's my first challenge to you, to push you a little. Given everything I've taught you, can you finish the app?
Can you (either the XML or programmatic way) get text from
that EditText
, store it in the intent, pull it from the intent and display it in your new
activity?
I'll bet you can!
As an extra challenge make the text big and loud by boosting the font-size.
I'm not going to totally leave you hanging. You should try to finish this up on your own and I hope that like me you get hung up on something for a while and spend a half-hour in the docs, debugging, and trying to figure out what is wrong.
But if you find yourself flailing and needing a way out, you can see the solution by going to the Github repo where these tutorials are located and looking at the source code of these apps. Every tutorial will have a fully functioning app or two to go along with it. If you get lost, you can compare what you have to mine and go from there.
(Hint: The thing that I got hung up on had everything to do with EditText.getText
. Good luck!)
Here's what the finished result should look like:
Conclusion
OK! We've come a long way and gotten into a lot of stuff! My hope for you is that at this point you understand the basic idea behind activities, views, and intents. I hope that you have started to learn how to use the docs and you've put the guides by your nightstand for a bit of light reading.
If you're not sure about these things then I recommend you get a night's sleep and come back to this tomorrow. If this is all very new to you (as it was to me!) then a second pass through it all is a great idea.
If you're raring to go then just keep on clicking through the lessons. Next up we'll be learning about some of the great tools that already exist to make Android development a lot more enjoyable.