Develop an application in Android using the Xtend language
Download latest Android SDK Bundle:
Download the full eclipse with xtend distro from here:
Using the xtend version of eclipse, install the android SDK into it using this update site:
Create a standard Android Application called 'yambax', with the following characteristics:
Make sure you can launch this application in the emulator
Bring in these string reources:
<resources>
<string name="app_name">Yamba X</string>
<string name="titleYamba">YambaX</string>
<string name="titleStatus">Status Update</string>
<string name="hintText">Please enter your 140-character status</string>
<string name="buttonUpdate">Update</string>
</resources>
... and this layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<!-- Title TextView-->
<TextView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:gravity="center"
android:textSize="30sp"
android:layout_margin="10dp" android:text="@string/titleStatus"/>
<!-- Status EditText -->
<EditText android:layout_width="fill_parent"
android:layout_height="0dp" android:layout_weight="1"
android:hint="@string/hintText" android:id="@+id/editText"
android:gravity="top|center_horizontal"></EditText>
<!-- Update Button -->
<Button android:layout_width="fill_parent"
android:layout_height="wrap_content" android:text="@string/buttonUpdate"
android:textSize="20sp" android:id="@+id/buttonUpdate"></Button>
</LinearLayout>
Download this jar archive :
and copy to the lib
folder of your android project.
This is a lightweight java-twitter library, based on this project here:
Replace your StatusActivity with the following:
package com.marakana.yambax;
import winterwell.jtwitter.Twitter;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class StatusActivity extends Activity implements OnClickListener
{
private static final String TAG = "StatusActivity";
EditText editText;
Button updateButton;
Twitter twitter;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_status);
editText = (EditText) findViewById(R.id.editText);
updateButton = (Button) findViewById(R.id.buttonUpdate);
updateButton.setOnClickListener(this);
twitter = new Twitter("student", "password");
twitter.setAPIRootUrl("http://yamba.marakana.com/api");
}
public void onClick(View v)
{
twitter.setStatus(editText.getText().toString());
Log.d(TAG, "onClicked");
}
}
Launch and you should see:
If you attempt a status update, your application may close. This is caused by an attempt to perform network access on the main thread (inside the twitter.setStatus method). For test purposes, we can disable this:
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
Run the app again and attempt to update status again. The status should appear here:
Add xtext nature to the project by selecting Project Properties->Configure->Add Xtext Nature
Then in settings change the generated java folder to align with ADKs expectations (gen):
To prime the project with the correct libraries, create a new XTend class call Test
in the com.marakana.yambax
package:
package com.marakana.yambax
class Test {
}
This will have an error on the package declaration. Select the error and it will prompt with "Add Xtext Lbraries to the project". Select this, and the libraries will be added. Your project will look like this:
Look carefully at the three jar files added under 'Xtend Library'. You will need to locate these files using explorer/finder, and copy them to the lib
folder of your project.
Delete the "Xtext Library" artifact from the project by selecting it and then selecting 'remove from build path'.
The project should resemble the following:
Finally, delete the current classes in com.marakana.com and replace them with the following:
package com.marakana.yambax
import android.app.Activity
import android.os.Bundle
import android.widget.EditText
import android.widget.Button
import android.view.View.OnClickListener
import android.view.View
import winterwell.jtwitter.Twitter
import android.util.Log
import android.os.StrictMode
class StatusActivity extends Activity implements OnClickListener
{
val TAG = "StatusActivity"
var EditText editText
var Button updateButton
var twitter = new Twitter("student", "password")
override onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_status)
val policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
editText = findViewById(R.id.editText) as EditText
updateButton = findViewById(R.id.buttonUpdate) as Button
updateButton.setOnClickListener(this)
twitter.setAPIRootUrl("http://yamba.marakana.com/api")
}
override onClick(View arg0)
{
twitter.setStatus(editText.getText.toString)
Log.d(TAG, "onClicked")
}
}
Make sure the application has Internet Permissions enabled in the manifest:
<uses-permission android:name="android.permission.INTERNET" />
You should be able to launch this application on the emulator, enter some text and press 'Update'. Expect to see your text on this site:
Create a new package com.marakana.utils
containing an xtend class to encapsulate the twitter API:
package com.marakana.utils
import winterwell.jtwitter.Twitter
class TwitterAPI
{
var Twitter twitter
new ()
{
this.twitter = new Twitter("student", "password")
twitter.setAPIRootUrl("http://yamba.marakana.com/api")
}
def String updateStatus (String status)
{
val result = twitter.updateStatus(status)
result.text
}
}
Introduce an AsyncTask com.marakana.utils
to use this API:
package com.marakana.yambax
import android.os.AsyncTask
import android.util.Log
import winterwell.jtwitter.TwitterException
import android.widget.Toast
import android.app.Activity
import com.marakana.utils.TwitterAPI
class TwitterPoster extends AsyncTask<String, Integer, String>
{
var TwitterAPI twitter
var Activity activity
new(TwitterAPI twitter, Activity activity)
{
this.twitter = twitter
this.activity = activity
}
override doInBackground(String... it)
{
try
{
var status = twitter.updateStatus(get(0))
status
}
catch (TwitterException e)
{
Log.e("YAMBA", e.toString());
"Failed to post";
}
}
override onPostExecute(String result)
{
Toast.makeText(activity, result, Toast.LENGTH_LONG).show();
}
}
Now we can reimplement StatusUpdate activity, using a Lambda to handle the button event:
package com.marakana.yambax
import android.app.Activity
import android.os.Bundle
import android.widget.EditText
import android.widget.Button
import android.util.Log
import com.marakana.utils.TwitterAPI
class StatusActivity extends Activity
{
val twitter = new TwitterAPI
override onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_status)
val editText = findViewById(R.id.editText) as EditText
val updateButton = findViewById(R.id.buttonUpdate) as Button
updateButton.setOnClickListener = [
val twitterPoster = new TwitterPoster(twitter, this)
twitterPoster.execute(editText.getText().toString())
Log.d("YAMBA", "onClicked")
]
}
}
Notice we no longer need to disable strict mode, as we are not posting from a background task.
Verify this works as expected, and that the status appears on:
A toast message should also appear if the update was successful
Introduce the following new strings into res/values/strings.xml
:
<string name="titlePrefs">Preferences</string>
<string name="titleUsername">Username</string>
<string name="titlePassword">Password</string>
<string name="titleApiRoot">API Root</string>
<string name="summaryUsername">Please enter your username</string>
<string name="summaryPassword">Please enter your password</string>
<string name="summaryApiRoot">URL of Root API for your service</string>
This will allow us to define a res/menus/menu.xml
resource:
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/itemPrefs" android:title="@string/titlePrefs"
android:icon="@android:drawable/ic_menu_preferences"></item>
</menu>
Now we can create a PreferencesActivity. Introduce a new folder in res
called xml
, and create this file in there:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<EditTextPreference android:title="@string/titleUsername"
android:summary="@string/summaryUsername" android:key="username"></EditTextPreference>
<EditTextPreference android:title="@string/titlePassword"
android:password="true" android:summary="@string/summaryPassword"
android:key="password"></EditTextPreference>
<EditTextPreference android:title="@string/titleApiRoot"
android:summary="@string/summaryApiRoot" android:key="apiRoot"></EditTextPreference>
</PreferenceScreen>
Create a new PrefsActivity
activity class in com.marakana.yambax
:
package com.marakana.yambax
import android.os.Bundle
import android.preference.PreferenceActivity;
class PrefsActivity extends PreferenceActivity
{
override onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.prefs);
}
}
We can display a menu and the PrefsActivity in the StatusUdate activity:
override onCreateOptionsMenu(Menu menu)
{
getMenuInflater.inflate(R.menu.menu, menu)
true
}
override onOptionsItemSelected(MenuItem item)
{
startActivity(new Intent(this, typeof(PrefsActivity)))
true
}
Finally, make sure the activity is referenced in the manifest:
<activity android:name=".PrefsActivity" android:label="@string/titlePrefs" />
When the menu is selected, we should see something like this:
Define a new TwitterAPI, which allows account details to be changed:
package com.marakana.utils
import winterwell.jtwitter.Twitter
import android.content.SharedPreferences
class TwitterAPI
{
var Twitter twitter
new (String username, String password, String root)
{
this.twitter = new Twitter(username, password)
twitter.setAPIRootUrl(root)
}
def changeAccount(SharedPreferences prefs)
{
val username = prefs.getString("username", "student")
val password = prefs.getString("password", "password")
val root = prefs.getString("apiRoot", "http://yamba.marakana.com/api")
this.twitter = new Twitter(username, password)
twitter.setAPIRootUrl(root)
}
def String updateStatus (String status)
{
val result = twitter.updateStatus(status)
result.text
}
}
This is a revised version of StatusUpdate to use preferences and this new api:
package com.marakana.yambax
import android.app.Activity
import android.os.Bundle
import android.widget.EditText
import android.widget.Button
import com.marakana.utils.TwitterAPI
import android.view.Menu
import android.view.MenuItem
import android.content.Intent
import android.view.View.OnClickListener
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.content.SharedPreferences
import android.preference.PreferenceManager
class StatusActivity extends Activity
{
var EditText editText
var Button updateButton
var TwitterAPI twitter
var update = [ new TwitterPoster(twitter, this).execute(editText.getText.toString) ] as OnClickListener
var prefsChanged = [ SharedPreferences prefs, String s |
twitter.changeAccount(prefs) ] as OnSharedPreferenceChangeListener
override onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_status)
twitter = new TwitterAPI("student", "password", "http://yamba.marakana.com/api")
editText = findViewById(R.id.editText) as EditText
updateButton = findViewById(R.id.buttonUpdate) as Button
updateButton.setOnClickListener = update
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener = prefsChanged
}
override onCreateOptionsMenu(Menu menu)
{
getMenuInflater.inflate(R.menu.menu, menu)
true
}
override onOptionsItemSelected(MenuItem item)
{
startActivity(new Intent(this, typeof(PrefsActivity)))
true
}
}
Notice the lambdas
declared as part of the class definition.
Run the application now. You should be able to tweet as before, but now also be able to use a different account. These accounts can be created on :
Introduce a new Application class, which will manage the preferences settings:
package com.marakana.yambax
import com.marakana.utils.TwitterAPI
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.app.Application
import android.preference.PreferenceManager
class YambaApplication extends Application
{
@Property TwitterAPI twitter
var prefsChanged = [ SharedPreferences prefs, String s|
twitter.changeAccount(prefs) ] as OnSharedPreferenceChangeListener
override onCreate()
{
super.onCreate
twitter = new TwitterAPI("student", "password", "http://yamba.marakana.com/api")
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener = prefsChanged
}
override onTerminate()
{
super.onTerminate
}
}
In this version, we can rework to TwitterPoster to use the TwitterAPI in the ApplicationObject:
package com.marakana.yambax
import android.os.AsyncTask
import android.util.Log
import winterwell.jtwitter.TwitterException
import android.widget.Toast
import android.app.Activity
import com.marakana.utils.TwitterAPI
class TwitterPoster extends AsyncTask<String, Integer, String>
{
val TwitterAPI twitter
val Activity activity
new(Activity activity)
{
var app = activity.getApplication() as YambaApplication
this.twitter = app.twitter
this.activity = activity
}
override doInBackground(String... it)
{
try
{
var status = twitter.updateStatus(get(0))
status
}
catch (TwitterException e)
{
Log.e("YAMBA", e.toString());
"Failed to post";
}
}
override onPostExecute(String result)
{
Toast.makeText(activity, result, Toast.LENGTH_LONG).show();
}
}
We can now simplify the StatusUpdate class:
package com.marakana.yambax
import android.app.Activity
import android.os.Bundle
import android.widget.EditText
import android.widget.Button
import android.view.Menu
import android.view.MenuItem
import android.content.Intent
import android.view.View.OnClickListener
class StatusActivity extends Activity
{
var EditText editText
var Button updateButton
var update = [ new TwitterPoster(this).execute(editText.getText.toString) ] as OnClickListener
override onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_status)
editText = findViewById(R.id.editText) as EditText
updateButton = findViewById(R.id.buttonUpdate) as Button
updateButton.setOnClickListener = update
}
override onCreateOptionsMenu(Menu menu)
{
getMenuInflater.inflate(R.menu.menu, menu)
true
}
override onOptionsItemSelected(MenuItem item)
{
startActivity(new Intent(this, typeof(PrefsActivity)))
true
}
}
We define the ApplicationObject in the manifest:
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:name="com.marakana.yambax.YambaApplication">
Run the application now and it should work as before.