Assignments

1a. Creating a new message screen

  1. Create a new project in Android Studio. Name your Activity NewMessageActivity.
  2. We will now create a label for the message sender input field. Go to the layout file (activity_new_message.xml) and add TextView component inside the root LinearLayout. Give it a width of wrap_content (as wide as the text inside it plus paddings etc.) and height of wrap_content (will be as big as the text content plus paddings etc.). To show the label text, add the android:text attribute and refer to a string in the res/values/strings.xml file by using the syntax @string/your_string. You will need to create this string in the strings resource file.
  3. Create the input field for message sender by adding an EditText component inside the root LinearLayout. Give it a width of match_parent (fill the screen from left to right) and height of wrap_content. To be able to access the input text in the activity, we need to give the edittext an android:id attribute. We will use a special syntax when creating this id for the first time: @+id/your_id. Later on, if you need to refer to this component you can skip the + sign.
  4. Repeat the two above steps for creating a label and input field for the message text.
  5. Add a button component for sending the message. Give it wrap_content for width and height. Give it an id. Give it the text "send" (use the strings.xml file).
  6. By now, you should have two pairs of TextView and EditText and a send button, and the EditTexts and the button should each have an id. To test your app, Android needs to know which activity to launch when your app is started. To do this you must make sure your activity is registered in the manifest file and that an intent filter with action MAIN and category LAUNCHER is specified. Then start your app by clicking Run -> Debug and select "Launch emulator".
        <activity android:name=".NewMessageActivity">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN"/>
                    <category android:name="android.intent.category.LAUNCHER"/>
                </intent-filter>
            </activity>
        
  7. To send the message we need to respond to pressing the send button. Go to NewMessageActivity and inside the onCreate method, use the activity method View findViewById(int id) to get a reference to the button. The id will be available on the global R object by R.id.your_button_id. Do the same with the sender and message EditText. Remember to cast the generic views to EditText and Button. Use setOnClickListener on the button and inside the onClick callback you can get the text of the sender and message by using getText().toString() on the instances. To test everything, display a Toast message with the sender and message, and test using the emulator/device.

1b. Sending new messages to the API

  1. Create a MessageApi interface using Retrofit with a send operation that accepts a sender and a message.
  2. Use a Singleton to obtain an instance in your activity of the MessageApi using a Retrofit object. See example in the wiki chapter "Consume REST APIs with Retrofit". Use the base url http://mobile-course.herokuapp.com/.
  3. Call your send operation in the click listener where you previously displayed a toast.
  4. Test that sending works by opening http://mobile-course.herokuapp.com/ in a browser

2. Displaying the messages

  1. Create a MessagesActivity and corresponding layout. Add a RecyclerView to the layout and get a reference to it in the activity.
  2. compile 'com.android.support:recyclerview-v7:+'
  3. Create a layout file for displaying messages in the list, f. ex /res/layout/item_message.xml, and put in some views to display the message sender, text and date.
  4. Create a MessagesAdapter class that extends RecyclerView.Adapter and implement the required methods.
  5. Use Retrofit to create a client for the REST API and fetch the messages asynchronously.
  6. Add the received messages to the adapter and notify it of the changed message list.

Hints

  • The ViewHolder you create should contain references to all the TextViews you need to show for each position in the message list.
  • You will need a LayoutInflater in MessagesAdapter in onCreateViewHolder to create the layout view of the message. It is probably easiest to use Activitys getLayoutInflater() and pass it to the adapter.
  • Store a reference to the list of messages in MessagesAdapter. When you have added some messages to the list, call notifyDataSetChanged on the adapter, and it will render the messages again.

3. Show images

  1. Add an ImageView in your message layout (e.g. item_message.xml).
  2. In onBindViewHolder in MessagesAdapter, use the Picasso image loader library to load the image from a url if it exists for that message.

Hints

  • Add the Picasso dependency to your app.gradle file: compile 'com.squareup.picasso:picasso:2.5.2'.

4. Use the camera to add images to your messages

  1. Add a button in your new message layout and add an OnClickListener for it in NewMessageActivity.
  2. In the listener, start the image capture by using an Intent.
  3. Override onActivityResult(...) to receive to captured photo.
  4. Use the provided ImgurUpload class to upload the photo to Imgur.
  5. Set the imgur url on the message and post it as before.

Create a file to store the captured photo

Use getExternalFilesDir(Environment.DIRECTORY_PICTURES) to get a directory where you can create a file for storing the captured photo.

Create an intent to start the camera

  Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
  intent.putExtra(MediaStore.EXTRA_OUTPUT, imageFileUri);
  int REQUEST_IMAGE_CAPTURE = 0; // Some constant
  startActivityForResult(intent, REQUEST_IMAGE_CAPTURE);
  

Receive the captured photo

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
    BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
    bmpFactoryOptions.inJustDecodeBounds = false;
    BitmapFactory.decodeFile(imageFile.getPath(), bmpFactoryOptions);

    bmpFactoryOptions = new BitmapFactory.Options();
    int inSampleSize = 4; // Scale the image to a quarter of the original size
    bmpFactoryOptions.inSampleSize = inSampleSize;
    Bitmap scaledBitmap = BitmapFactory.decodeFile(imageFile.getPath(), bmpFactoryOptions);

    // Upload bitmap to Imgur
  }
}

5. Improve the design or implement more API operations. Choose 5a or 5b.

5a. Improve design

  1. Use a android.support.v7.widget.CardView in the item_message layout and make it look good.
  2. Add a Lollipop toolbar to your app by using the support library (android.support.v7.widget.Toolbar). Put it in a separate layout file so that you can include it in all activities. Style it with a theme.
  3. Use com.amulyakhare:com.amulyakhare.textdrawable to display a colored box for each message with the first letter of recipients name, ala. Gmail. Use ColorGenerator with material design colors.
  4. By using the library com.github.shell-software:fab, add a FAB button (again, like Gmail) for creating a new message.

5b. Edit and delete messages

  1. Add an EditMessageActivity. Try to reuse the layout for NewMessageActivity as much as you can.
  2. Implement delete. You can f. ex have add a delete button in EditMessageActivity or delete by long-pressing messages in the list.

Hints

  • To pass objects between activities in intents, you can serialize them using Gson, and pass the serialized string instead. Convert back from json to java at the destination activity.

API

The server is at: http://mobile-course.herokuapp.com

It hosts the following JSON endpoints:

URL HTTP verb Description
http://mobile-course.herokuapp.com/message GET Retrieves all messages
http://mobile-course.herokuapp.com/message POST Creates a message(see JSON example below)
http://mobile-course.herokuapp.com/message/:id PUT Updates a message
http://mobile-course.herokuapp.com/message/:id DELETE Deletes a message

POST example

Uploading to Imgur

Retrofit

If you want to use retrofit, you will need to add a gradle dependency. The follow versions of Retrofit and the Gson converter were used when creating this workshop:
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
For updated versions, feel free to check out the Retrofit homepage.