Lab archive: lab-2.zip Solved tasks: lab-2-solved.zip
Create a new project and add a button to the main activity layout. Get a reference to this button in the onCreate method in MainActivity. Add a new Activity to the project. You can add a new activity automatically by right clicking on the project and selecting New > Android Activity from the menu. This will start a wizard similar to the one at application creation. In the new Activity override the onCreate method and use the setContentView method to change the layout to the layout you just created.
Back in MainActivity, add code to the onClick of the OnClickListener so that the new activity gets started. To start a new Activity you have to create an intent first. An intent to launch an activity explicitly takes two parameters: a Context and the Class of the second activity. Use getBaseContext() or v.getContext() to obtain a context.
public void onClick(View v) { Intent i = new Intent(getBaseContext(), NewActivity.class); }
Then, to launch the activity you must call:
startActivity(i);
Before launching the new activity, it must be declared in AndroidManifest.xml file. If you create the activity manually, you will have to add a similar line to the following one to the manifest file. If you created the activity through the wizard, it should have been added automatically.
<activity android:name="com.example.test.NewActivity" />
In the onCreate of the second activity make sure you use setContentView to the layout of the second activity and call the onCreate of the super class. If you created the Activity manually, you will also have to create an empty layout.
super.onCreate(savedInstanceState); setContentView(R.layout.new_activity);
Intents also contains a dictionary of data. Add an EditText box to your main activity layout and a TextView to your second activity layout. When clicking the button, get the text from the EditText element, add it to the intent. Then, in your second activity, get the string from the intent and display it in the TextView. (Hint: Intent.putExtra, Intent.getStringExtra and Activity.getIntent)
Lists contain multiple items. Each item in the list has a layout, and usually all items in a list have the same layout. To account for this, ListViews must have an adapter which creates the View for each item. Usually it's easiest to extend ArrayAdapter, since it already has methods to add or remove items from the list (unless you have different types of items).
Add a ListView to your second activity. You will use the list_item.xml layout, MenuItem.java and MenuAdapter.java classes found in the lab archive. You have to implement the getView method of the MenuAdapter class such that it will properly display items of the MenuItem type. Look for the TODO comments.
You can use the layout inflater to create the views (or reuse the View the method receives, but make sure it's not null first):
LayoutInflater inflater = (LayoutInflater)context.getSystemService (Context.LAYOUT_INFLATER_SERVICE);
To inflate a view use the inflater you obtained and call the inflate method, giving it the list_item layout, and a null ViewGroup. Then, set the correct text on the TextViews inside the new View. Use findViewById on the View to get the TextViews.
Use this code to test your getView implementation:
List<MenuItem> items = new ArrayList<MenuItem>(); ListView lv = (ListView) findViewById(R.id.listView1); ArrayAdapter<MenuItem> list = new MenuAdapter(this, items); list.add(new MenuItem("Coffee", 6, "Simple black coffee")); list.add(new MenuItem("Caffe latte", 7, "Coffee with milk")); list.add(new MenuItem("Espresso", 7, "Simple espresso")); list.add(new MenuItem("Caffe macchiatto", 8, "Espresso with foamed milk")); lv.setAdapter(list);
Add another two EditText elements to your first activity: one accepting strings and another accepting an integer. Add another button, label it “Add”. You will use this button to insert items into a database using a Content Provider, and the second activity will query this content provider. To add a Content Provider right click on the project folder and select New > Other and from here, select Android > Android Object and then Content Provider. You will need to provide an authority, typically this is the class name and package of the Content Manager to avoid naming issues with other apps.
It should generate a new ContentProvider. A ContentProvider does not need a specific backend, it can be used just as well for databases or data from files or the internet. In this task we will create an interface over a SQLite database.
First, there is some setup involved for convenience:
private static final UriMatcher sUriMatcher; static int TABLE = 1; static int ROW = 2; static { sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); // first parameter should be the authority declared in the manifest sUriMatcher.addURI("com.example.test.MyContentProvider", "menu", TABLE); sUriMatcher.addURI("com.example.test.MyContentProvider", "menu/#", ROW); }
This will generate an URI Matcher which will help identify what kind of requests need to be processed.
Secondly, again, for convenience, a SQLiteOpenHelper class should be created.
static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) { // context, db name, cursor factory - null for default, db version super(context, "sample.db", null, 1); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE IF NOT EXISTS menu (" + "id INTEGER PRIMARY KEY," + "name TEXT," + "desc TEXT," + "price INTEGER" + ");"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { //This should repopulate the table in the new database as well db.execSQL("DROP TABLE IF EXISTS menu"); onCreate(db); } }
Then, in onCreate you have to create an instance of this helper class and return true.
static DatabaseHelper dbHelper; public boolean onCreate() { dbHelper = new DatabaseHelper(getContext()); return true; }
Your task is to implement insert and query. Insert should check the ContentValue (only if the URI matches the table, not a row), insert it into the table, and return the new row's URI. Query should return the rows in the table which match the given parameters (including the ID, if it's given in the URI) and return the cursor.
Use the DatabaseHelper to access the database (see getReadableDatabase, getWritableDatabase) and SQLiteQueryBuilder to create a query. Use the UriMatcher to check URI type and validity.
Then, to test, insert items into the database in the MainActivity, and query the database and build the list from the database in the second activity.
Example inserting an item into the database:
b2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ContentValues cv = new ContentValues(); cv.put("name", ed1.getText().toString()); cv.put("desc", ed2.getText().toString()); cv.put("price", Integer.decode(ed3.getText().toString())); Uri u = getContentResolver().insert(Uri.parse("content://com.example.test.MyContentProvider/menu/"), cv); Log.d("URI", u.toString()); } });
To add a Broadcast Receiver right click on the project folder and select New > Other and from here, select Android > Android Object and then Broadcast Receiver. By default it is created without an intent filter, but we will access it explicitly, so there is no need to add an intent filter.
Go back the adapter you created previously. Add an onClickListener to the view created in getView. Make it so that when you click on item it creates an Intent containing context and the receiver's class name and add the name of the clicked item using putExtra. Afterwards, use sendBroadcast to send the intent to the BroadcastReceiver.
Intent i = new Intent(context, MyReceiver.class); i.putExtra("name", getItem(position).getName()); context.sendBroadcast(i);
Inside the receiver, in the onReceive method, retrieve the String added to the intent and display it using a toast notification.
Toast.makeText(context, name, Toast.LENGTH_LONG).show();
There are multiple types of services (although they are not exclusive): started services and bound services. A started service receives commands, it runs them and when it's done with all the commands it stops. Bound services should be running as long as at least one client is still connected or until they are done with their tasks.
There are several ways to communicate with a bound service: you can either call functions from them directly if they're in the same application - for example, you bind to them and they generate data for you as long as they are running, you can call a function to retrieve data. This is useful when you have multiple activities which use the same data and you want to keep the service running during your application's lifetime. The second way is using a Messenger. This creates an interface similar to a socket where you assign a handler to call when you receive data. This allows for two way communication, and doesn't require you to use AIDL, although it uses it internally. The third way is to use AIDL, generate an interface and stubs and create a custom way to call functions in another process.
This task requires you to create a service and use Messenger to communicate between your activity and the service.
You have to create a new class which extends Handler and override handleMessage. In handleMessage you should check the value of the what field. This should indicate the type of message. You should define these values as static finals inside the Service. For now define three values: REQUEST_ACTION_LOCAL, REQUEST_ACTION_GLOBAL, REGISTER and UNREGISTER (1, 2, 3 and 4). In case of REGISTER or UNREGISTER, check the replyTo to field and add the Messenger to a list of Messengers or remove it. In case of REQUEST_ACTION_LOCAL send a message with a random integer to the Messenger in the replyTo field. In case of REQUEST_ACTION_GLOBAL send a message to all the registered Messengers. Add logs to all the cases, to see what the Service is doing. Also, remember to catch RemoteException (and remove from the list of messengers the ones that throw this exception when sending to them).
In the Activity, create a ServiceConnection object in which you create a Messenger object from the IBinder you receive in onServiceConnected, and set it to null in onServiceDisconnected.
Then bind to the service in onCreate (mConnection is the ServiceConnection):
bindService(new Intent(this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
Next, create a Messenger, and a handler, like previously. You should use the same codes as before. This time you only have to log the messages received from the service.
Use Message.obtain to get a Message. The handler parameter should be null, the second parameter should be the action you want to execute. You have to set the replyTo field separately, set it to the Messenger you have created in the activity (not the one you created from the IBinder). Next, call the send of the Messenger tied to the service.
Add four buttons: one to sent a REQUEST_ACTION_LOCAL, one for REQUEST_ACTION_GLOBAL, one for REGISTER and the other for UNREGISTER. Check if the output is what you expected.
Do unbindService when leaving the activity (onPause).