Create a currency converter application using Yahoo! API
DownloadKeywords: ConnectivityManager HttpGet XmlPullParser SAXParser SimpleCursorAdapter SQLiteDatabase ContentProvider SQLiteQueryBuilder BroadcastReceiver IntentService AChartEngine Search Dialog Animation TableLayout
Contents- Overview
- Create a new Eclipse Android project
- Define the Data model
- The Android Manifest file
- The Application class
- Create a Preferences screen
- Implement the Init Task
- The Yahoo! Finance API
- The XmlPullParser
- The SAX Parser
- Calling RESTful service
- Implement the Data Service
- Create a splash screen
- Create the Main screen
- The Search Dialog
- Create the Info screen
- The AChartEngine library
The MainActivity class is huge so we will implement it in parts. Here is the bare minimum code to display the currencies.
public class MainActivity extends ListActivity { private SQLiteDatabase db; private Resources res; private Spinner baseSpinner; private EditText baseEdit; private ImageView processImg; private Animation animation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.main); db = ForexWiz.db; res = getResources(); // TODO find views by id } @Override protected void onResume() { super.onResume(); final String baseCurrency = baseSpinner.getSelectedItem().toString(); Cursor c = db.rawQuery("SELECT * FROM symbol AS s, quote AS q WHERE q.pair = ? || s.code AND s.isTracked = 1", new String[]{baseCurrency}); startManagingCursor(c); SimpleCursorAdapter adapter = new SimpleCursorAdapter( this, R.layout.row, c, new String[]{"country", "name", "code"}, new int[]{R.id.text1, R.id.text2, R.id.text3}); adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder() { @Override public boolean setViewValue(View view, Cursor cursor, int columnIndex) { TextView tv = (TextView) view; switch(view.getId()) { case R.id.text1: // get the country flag image int left = res.getIdentifier(cursor.getString(columnIndex), "drawable", "com.appsrox.forexwiz"); tv.setCompoundDrawablesWithIntrinsicBounds(left, 0, 0, 0); tv.setCompoundDrawablePadding(10); return false; case R.id.text2: case R.id.text3: String currency = cursor.getString(cursor.getColumnIndex("code")); double price = cursor.getDouble(cursor.getColumnIndex("price")); double amt = Double.parseDouble(baseEdit.getText().toString()); if (view.getId() == R.id.text2) { tv.setText(String.format("%f %s = %f %s", amt, baseCurrency, amt*price, currency)); } else if (view.getId() == R.id.text3) { tv.setText(String.format("%f %s = %f %s", amt, currency, amt/price, baseCurrency)); } return true; } return false; } }); setListAdapter(adapter); } }Now let's add logic to update the quotes by starting DataService that we implemented earlier. Add this piece of code in onCreate() method.
Intent refresh = new Intent(getApplicationContext(), DataService.class); startService(refresh);Additionally, create a receiver to listen to the status broadcast by DataService.
private BroadcastReceiver statusReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent != null && Constants.ACTION_REFRESH.equals(intent.getAction())) { switch (intent.getIntExtra(Constants.EXTRA_STATUS, 100)) { case Constants.STATUS_FAILED: Toast.makeText(context, getString(R.string.update_failed), Toast.LENGTH_SHORT).show(); case Constants.STATUS_SUCCESS: runOnUiThread(new Runnable() { @Override public void run() { SimpleCursorAdapter adapter = (SimpleCursorAdapter) getListAdapter(); adapter.notifyDataSetChanged(); getListView().invalidateViews(); } }); break; } } } };As you can see we refresh the list view on successful update of quotes. We need to register the receiver in onResume() and unregister it in onPause() method.
@Override protected void onResume() { super.onResume(); registerReceiver(statusReceiver, new IntentFilter(Constants.ACTION_UPDATE)); // ... } @Override protected void onPause() { unregisterReceiver(statusReceiver); super.onPause(); }We can show a little animation while the update is going on. For this we'll use Animation provided by Android. Create a XML file under res/anim directory with the following content.
<?xml version="1.0" encoding="utf-8"?> <rotate xmlns:android="http://schemas.android.com/apk/res/android" android:fromDegrees="0" android:toDegrees="360" android:pivotX="50%" android:pivotY="50%" android:duration="2000" android:interpolator="@android:anim/linear_interpolator" android:repeatCount="infinite" android:repeatMode="restart"> </rotate>Next, load the animation in the onCreate() method.
animation = AnimationUtils.loadAnimation(this, R.anim.progress);Finally, start the animation before starting the DataService and stop it in the statusReceiver.
processImg.startAnimation(animation);
processImg.clearAnimation();There are few other things left in the MainActivity like setting listeners on the Spinner and EditText so the list view gets refreshed when user changes the base currency or amount. Add the following code in the onCreate() method.
baseSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { ForexWiz.setBaseCurrency(parent.getItemAtPosition(position).toString()); getListView().invalidateViews(); } @Override public void onNothingSelected(AdapterView<?> parent) {} }); baseEdit.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) {} @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override public void afterTextChanged(Editable s) { getListView().invalidateViews(); } });Also, onclick of a currency in the list view should launch a new screen to display details such as Ask, Bid, Change, etc. For this override onListItemClick() method in ListActivity.
@Override protected void onListItemClick(ListView l, View v, int position, long id) { Intent intent = new Intent(this, InfoActivity.class); intent.putExtra(Constants.EXTRA_ID, id); startActivity(intent); }Here is an useful tip for improving user experience of the app.
You can retain the scroll position of the ListView when the Activity is resumed or recreated by invoking setSelectionFromTop() on the ListView.
int lastViewedPosition, topOffset; @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("lastViewedPosition", lastViewedPosition); outState.putInt("topOffset", topOffset); } @Override protected void onRestoreInstanceState(Bundle state) { super.onRestoreInstanceState(state); lastViewedPosition = state.getInt("lastViewedPosition"); topOffset = state.getInt("topOffset"); } @Override protected void onResume() { super.onResume(); // ... getListView().setSelectionFromTop(lastViewedPosition, topOffset); } @Override protected void onPause() { // ... lastViewedPosition = getListView().getFirstVisiblePosition(); View v = getListView().getChildAt(0); topOffset = (v == null) ? 0 : v.getTop(); super.onPause(); }We need to implement a onClick() method which we declared in the layout as the onClick callback for the buttons.
public void onClick(View v) { switch (v.getId()) { case R.id.add_btn: onSearchRequested(); break; case R.id.settings_btn: Intent settings = new Intent(); settings.setClass(this, SettingsActivity.class); startActivity(settings); break; } }Last but not the least is to declare the activity in the manifest.
<activity android:name=".MainActivity"> <meta-data android:name="android.app.default_searchable" android:value=".SearchableActivity" /> </activity>
Did you notice search in onClick() method and in the manifest? We will next implement search in our application which will allow user to search a currency and add it to the list.