Java tutorial
package com.ibm.hellotodoadvanced; /** * Copyright 2016 IBM Corp. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import android.Manifest; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v4.widget.SwipeRefreshLayout; import android.util.Log; import android.view.Gravity; import android.view.View; import android.widget.AdapterView; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; import com.ibm.mobilefirstplatform.clientsdk.android.core.api.Request; import com.ibm.mobilefirstplatform.clientsdk.android.core.api.BMSClient; import com.ibm.mobilefirstplatform.clientsdk.android.core.api.Response; import com.ibm.mobilefirstplatform.clientsdk.android.core.api.ResponseListener; import com.ibm.mobilefirstplatform.clientsdk.android.push.api.MFPPush; import com.ibm.mobilefirstplatform.clientsdk.android.push.api.MFPPushException; import com.ibm.mobilefirstplatform.clientsdk.android.push.api.MFPPushNotificationListener; import com.ibm.mobilefirstplatform.clientsdk.android.push.api.MFPPushResponseListener; import com.ibm.mobilefirstplatform.clientsdk.android.push.api.MFPSimplePushNotification; import com.ibm.mobilefirstplatform.clientsdk.android.security.api.AuthorizationManager; import com.ibm.mobilefirstplatform.clientsdk.android.security.facebookauthentication.FacebookAuthenticationManager; import org.json.JSONArray; import org.json.JSONObject; import java.io.PrintWriter; import java.io.StringWriter; import java.net.MalformedURLException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * The {@code MainActivity} is the primary visual activity shown when the app is being interacted with. * The ResponseListener interface is implemented to handle Mobile Client Access authentication and related responses. */ public class MainActivity extends Activity implements ResponseListener { private static final String TAG = MainActivity.class.getSimpleName(); private static final int PERMISSION_REQUEST_GET_ACCOUNTS = 0; private ListView mListView; // Main ListView private List<TodoItem> mTodoItemList; // The list of TodoItems private TodoItemAdapter mTodoItemAdapter; // Adapter for bridging the list of TodoItems with the ListView private SwipeRefreshLayout mSwipeLayout; // Swipe down refresh to update local app if backend has changed private BMSClient bmsClient; // Bluemix Mobile Services Client SDK private MFPPush push; // Push Client private MFPPushNotificationListener notificationListener; // Notification listener to handle push notifications sent to the application @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); bmsClient = BMSClient.getInstance(); try { //initialize SDK with IBM Bluemix application ID and route //You can find your backendRoute and backendGUID in the Mobile Options section on top of your Bluemix application dashboard //TODO: Please replace <APPLICATION_ROUTE> with a valid ApplicationRoute and <APPLICATION_ID> with a valid ApplicationId bmsClient.initialize(this, "<APPLICATION_ROUTE>", "<APPLICATION_ID>"); } catch (MalformedURLException exception) { throw new RuntimeException(exception); } //initialize UI initListView(); initSwipeRefresh(); // initialize IBM Push Notifications SDK initPush(); } /** * Initializes the main list view and sets long click listener for delete. * Note: the Node delete endpoint is protected by Mobile Client Access and can only be accessed with an authorization header from the Bluemix Mobile Services Client SDK. */ private void initListView() { // Get MainActivity's ListView mListView = (ListView) findViewById(R.id.listView); // Init array to hold TodoItems mTodoItemList = new ArrayList<>(); // Create and set ListView adapter for displaying TodoItems mTodoItemAdapter = new TodoItemAdapter(getBaseContext(), mTodoItemList); mListView.setAdapter(mTodoItemAdapter); // Set long click listener for deleting TodoItems mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(android.widget.AdapterView<?> parent, View view, int position, long id) { // Grab TodoItem to delete from current showing list TodoItem todoItem = mTodoItemList.get(position); // Grab TodoItem id number and append to the DELETE REST request using the Bluemix Mobile Services Client SDK String todoId = Integer.toString(todoItem.idNumber); Request request = new Request(bmsClient.getBluemixAppRoute() + "/api/Items/" + todoId, Request.DELETE); // Send the request and use the response listener to react request.send(getApplicationContext(), new ResponseListener() { // Update the list if successful @Override public void onSuccess(Response response) { Log.i(TAG, "Item deleted successfully"); loadList(); } // If the request fails, log errors @Override public void onFailure(Response response, Throwable throwable, JSONObject extendedInfo) { String errorMessage = ""; // different responses can cause different parameters to be null, be sure to check for those cases if (response != null) { errorMessage += response.toString() + "\n"; } if (throwable != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); throwable.printStackTrace(pw); errorMessage += "THROWN" + sw.toString() + "\n"; } if (extendedInfo != null) { errorMessage += "EXTENDED_INFO" + extendedInfo.toString() + "\n"; } if (errorMessage.isEmpty()) errorMessage = "Request Failed With Unknown Error."; Log.e(TAG, "deleteItem failed with error: " + errorMessage); } }); return true; } }); } /** * Enables swipe down refresh for the list */ private void initSwipeRefresh() { mSwipeLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container); // Set swipe refresh listener to update the local list on swipe down mSwipeLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { loadList(); } }); } /** * Initializes the IBM Push Notifications SDK and creates notification listener to handle incoming push notifications. */ private void initPush() { // Initialize Push client using this activity as the context push = MFPPush.getInstance(); push.initialize(this); // Create notification listener and enable pop up alert notification when a message is received // Note: You may see some errors in the logs on notification receipt indicating missing values. These are non-fatal and can be ignored. notificationListener = new MFPPushNotificationListener() { @Override public void onReceive(final MFPSimplePushNotification message) { // The entire message is printed in the log for your understanding Log.i(TAG, "Received a Push Notification: " + message.toString()); runOnUiThread(new Runnable() { public void run() { new AlertDialog.Builder(MainActivity.this).setTitle("Received a Push Notification") .setMessage(message.getAlert()) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // Make sure most up to date cloud data is displayed when notification is dismissed. loadList(); } }).show(); } }); } }; } @Override protected void onResume() { super.onResume(); // Calling the auth initialization code in onResume ensures that authentication is required whenever the app enters the foreground initAuth(); } /** * Handles configuring and starting authentication. * If Facebook auth is configured properly, a public FB login will be required before the authorization header can be obtained */ private void initAuth() { // Register this activity to handle Facebook auth response using the ResponseListener interface FacebookAuthenticationManager.getInstance().register(this); // Attempting to obtain an authorization header kicks off the Facebook login process. If successful, the onSuccess() function is called and the authorization header is cached to be used on outbound requests. // Note: if no auth is configured in the Bluemix MCA instance, the authentication will succeed automatically since it only checks that the request is coming from a Bluemix Mobile Services core SDK. AuthorizationManager.getInstance().obtainAuthorizationHeader(this, this); } /** * Handles successful authentication against MCA. * @param response HTTP response object from MCA. */ @Override public void onSuccess(Response response) { Log.i(TAG, "Successfully authenticated against MCA: " + response.getResponseText()); // Register for push notifications and show data now that the user is authenticated registerForPush(); loadList(); } /** * Registers device for push notifications and, if successful, the IBM Push Notifications SDK begins listening to the notification listener (created in initPush) to handle incoming push notifications. */ private void registerForPush() { Log.i(TAG, "Registering for push notifications"); // Creates response listener to handle the response when a device is registered. MFPPushResponseListener registrationResponselistener = new MFPPushResponseListener<String>() { @Override public void onSuccess(String response) { Log.i(TAG, "Successfully registered for push notifications, String: " + response); // Begin listening push.listen(notificationListener); } @Override public void onFailure(MFPPushException exception) { Log.e(TAG, "Error registering for push notifications: " + exception.getErrorMessage()); // Set null on failure so the sdk does not need to hold notifications push = null; } }; // Attempt to register device using response listener created above push.register(registrationResponselistener); } /** * Uses Bluemix Mobile Services SDK to GET the TodoItems from Bluemix and updates the local list. */ private void loadList() { // Send GET Request to Bluemix backend to retreive item list with response listener Request request = new Request(bmsClient.getBluemixAppRoute() + "/api/Items", Request.GET); request.send(getApplicationContext(), new ResponseListener() { // Loop through JSON response and create local TodoItems if successful @Override public void onSuccess(Response response) { if (response.getStatus() != 200) { Log.e(TAG, "Error pulling items from Bluemix: " + response.toString()); } else { try { mTodoItemList.clear(); JSONArray jsonArray = new JSONArray(response.getResponseText()); for (int i = 0; i < jsonArray.length(); i++) { JSONObject tempTodoJSON = jsonArray.getJSONObject(i); TodoItem tempTodo = new TodoItem(); tempTodo.idNumber = tempTodoJSON.getInt("id"); tempTodo.text = tempTodoJSON.getString("text"); tempTodo.isDone = tempTodoJSON.getBoolean("isDone"); mTodoItemList.add(tempTodo); } // Need to notify adapter on main thread in order for list changes to update visually runOnUiThread(new Runnable() { @Override public void run() { mTodoItemAdapter.notifyDataSetChanged(); Log.i(TAG, "List updated successfully"); if (mSwipeLayout.isRefreshing()) { mSwipeLayout.setRefreshing(false); } } }); } catch (Exception exception) { Log.e(TAG, "Error reading response JSON: " + exception.getLocalizedMessage()); } } } // Log Errors on failure @Override public void onFailure(Response response, Throwable throwable, JSONObject extendedInfo) { String errorMessage = ""; if (response != null) { errorMessage += response.toString() + "\n"; } if (throwable != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); throwable.printStackTrace(pw); errorMessage += "THROWN" + sw.toString() + "\n"; } if (extendedInfo != null) { errorMessage += "EXTENDED_INFO" + extendedInfo.toString() + "\n"; } if (errorMessage.isEmpty()) errorMessage = "Request Failed With Unknown Error."; Log.e(TAG, "loadList failed with error: " + errorMessage); } }); } /** * Hands off result code from Facebook login activity to Facebook SDK to verify login * This override is required for each form of auth (Facebook auth manager vs Google auth manager etc...). */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); FacebookAuthenticationManager.getInstance().onActivityResultCalled(requestCode, resultCode, data); } /** * Clears list data when authentication against MCA fails and logs errors/response. * @param response HTTP response object from MCA */ @Override public void onFailure(Response response, Throwable throwable, JSONObject extendedInfo) { if (!mTodoItemAdapter.isEmpty()) { Log.i(TAG, "clearing list data since authentication failed"); mTodoItemList.clear(); mTodoItemAdapter.notifyDataSetChanged(); } String errorMessage = ""; // Check for 404s and unknown host exception since this is the first request made by the app if (response != null) { if (response.getStatus() == 404) { errorMessage += "Application Route not found at:\n" + BMSClient.getInstance().getBluemixAppRoute() + "\nPlease verify your Application Route and rebuild the app."; } else { errorMessage += response.toString() + "\n"; } } // Be sure to check for null pointers, any of the above parameters may be null depending on the failure. if (throwable != null) { if (throwable.getClass().equals(UnknownHostException.class)) { errorMessage = "Unable to access Bluemix host!\nPlease verify internet connectivity and try again."; } else { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); throwable.printStackTrace(pw); errorMessage += "THROWN" + sw.toString() + "\n"; } } if (extendedInfo != null) { errorMessage += "EXTENDED_INFO" + extendedInfo.toString() + "\n"; } if (errorMessage.isEmpty()) errorMessage = "Request Failed With Unknown Error."; Log.e(TAG, "Failed to authenticate against MCA: " + errorMessage); } /** * Launches a dialog for adding a new TodoItem. Called when plus button is tapped. * * @param view The plus button that is tapped. */ public void addTodo(View view) { final Dialog addDialog = new Dialog(this); // UI settings for dialog pop-up addDialog.setContentView(R.layout.add_edit_dialog); addDialog.setTitle("Add Todo"); TextView textView = (TextView) addDialog.findViewById(android.R.id.title); if (textView != null) { textView.setGravity(Gravity.CENTER); } addDialog.setCancelable(true); Button add = (Button) addDialog.findViewById(R.id.Add); addDialog.show(); // When done is pressed, send POST request to create TodoItem on Bluemix add.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { EditText itemToAdd = (EditText) addDialog.findViewById(R.id.todo); final String name = itemToAdd.getText().toString(); // If text was added, continue with normal operations if (!name.isEmpty()) { // Create JSON for new TodoItem, id should be 0 for new items String json = "{\"text\":\"" + name + "\",\"isDone\":false,\"id\":0}"; // Create POST request with the Bluemix Mobile Services SDK and set HTTP headers so Bluemix knows what to expect in the request Request request = new Request(bmsClient.getBluemixAppRoute() + "/api/Items", Request.POST); HashMap headers = new HashMap(); List<String> contentType = new ArrayList<>(); contentType.add("application/json"); List<String> accept = new ArrayList<>(); accept.add("Application/json"); headers.put("Content-Type", contentType); headers.put("Accept", accept); request.setHeaders(headers); request.send(getApplicationContext(), json, new ResponseListener() { // On success, update local list with new TodoItem @Override public void onSuccess(Response response) { Log.i(TAG, "Item " + name + " created successfully"); loadList(); } // On failure, log errors @Override public void onFailure(Response response, Throwable throwable, JSONObject extendedInfo) { String errorMessage = ""; if (response != null) { errorMessage += response.toString() + "\n"; } if (throwable != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); throwable.printStackTrace(pw); errorMessage += "THROWN" + sw.toString() + "\n"; } if (extendedInfo != null) { errorMessage += "EXTENDED_INFO" + extendedInfo.toString() + "\n"; } if (errorMessage.isEmpty()) errorMessage = "Request Failed With Unknown Error."; Log.e(TAG, "addTodo failed with error: " + errorMessage); } }); } // Close dialog when finished, or if no text was added addDialog.dismiss(); } }); } /** * Launches a dialog for updating the TodoItem name. Called when the list item is tapped. * * @param view The TodoItem that is tapped. */ public void editTodoName(View view) { // Gets position in list view of tapped item final Integer position = mListView.getPositionForView(view); final Dialog editDialog = new Dialog(this); // UI settings for dialog pop-up editDialog.setContentView(R.layout.add_edit_dialog); editDialog.setTitle("Edit Todo"); TextView textView = (TextView) editDialog.findViewById(android.R.id.title); if (textView != null) { textView.setGravity(Gravity.CENTER); } editDialog.setCancelable(true); EditText currentText = (EditText) editDialog.findViewById(R.id.todo); // Get selected TodoItem values final String name = mTodoItemList.get(position).text; final boolean isDone = mTodoItemList.get(position).isDone; final int id = mTodoItemList.get(position).idNumber; currentText.setText(name); Button editDone = (Button) editDialog.findViewById(R.id.Add); editDialog.show(); // When done is pressed, send PUT request to update TodoItem on Bluemix editDone.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { EditText editedText = (EditText) editDialog.findViewById(R.id.todo); final String updatedName = editedText.getText().toString(); // If new text is not empty, create JSON with updated info and send PUT request if (!updatedName.isEmpty()) { String json = "{\"text\":\"" + updatedName + "\",\"isDone\":" + isDone + ",\"id\":" + id + "}"; // Create PUT REST request using Bluemix Mobile Services SDK and set HTTP headers so Bluemix knows what to expect in the request Request request = new Request(bmsClient.getBluemixAppRoute() + "/api/Items", Request.PUT); HashMap headers = new HashMap(); List<String> contentType = new ArrayList<>(); contentType.add("application/json"); List<String> accept = new ArrayList<>(); accept.add("Application/json"); headers.put("Content-Type", contentType); headers.put("Accept", accept); request.setHeaders(headers); request.send(getApplicationContext(), json, new ResponseListener() { // On success, update local list with updated TodoItem @Override public void onSuccess(Response response) { Log.i(TAG, "Item " + updatedName + " updated successfully"); loadList(); } // On failure, log errors @Override public void onFailure(Response response, Throwable throwable, JSONObject extendedInfo) { String errorMessage = ""; if (response != null) { errorMessage += response.toString() + "\n"; } if (throwable != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); throwable.printStackTrace(pw); errorMessage += "THROWN" + sw.toString() + "\n"; } if (extendedInfo != null) { errorMessage += "EXTENDED_INFO" + extendedInfo.toString() + "\n"; } if (errorMessage.isEmpty()) errorMessage = "Request Failed With Unknown Error."; Log.e(TAG, "editTodoName failed with error: " + errorMessage); } }); } editDialog.dismiss(); } }); } /** * When TodoItem image is tapped, switch boolean isDone value to indicate current completion status. * The TodoItem is updated on Bluemix and then the list is refreshed to reflect new status. * Calls notifyAllDevices if an item is successfully completed. * * @param view The TodoItem that has been tapped. */ public void isDoneToggle(View view) { Integer position = mListView.getPositionForView(view); final TodoItem todoItem = mTodoItemList.get(position); final boolean isDone = !todoItem.isDone; String json = "{\"text\":\"" + todoItem.text + "\",\"isDone\":" + isDone + ",\"id\":" + todoItem.idNumber + "}"; // Create PUT REST request using the Bluemix Mobile Services SDK and set HTTP headers so Bluemix knows what to expect in the request Request request = new Request(bmsClient.getBluemixAppRoute() + "/api/Items", Request.PUT); HashMap headers = new HashMap(); List<String> contentType = new ArrayList<>(); contentType.add("application/json"); List<String> accept = new ArrayList<>(); accept.add("Application/json"); headers.put("Content-Type", contentType); headers.put("Accept", accept); request.setHeaders(headers); request.send(getApplicationContext(), json, new ResponseListener() { // On success, update local list with updated TodoItem, and call notifyAllDevices if marked complete. @Override public void onSuccess(Response response) { Log.i(TAG, todoItem.text + " completeness updated successfully"); loadList(); if (isDone) { notifyAllDevices(todoItem.text); } } // On failure, log errors @Override public void onFailure(Response response, Throwable throwable, JSONObject extendedInfo) { String errorMessage = ""; if (response != null) { errorMessage += response.toString() + "\n"; } if (throwable != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); throwable.printStackTrace(pw); errorMessage += "THROWN" + sw.toString() + "\n"; } if (extendedInfo != null) { errorMessage += "EXTENDED_INFO" + extendedInfo.toString() + "\n"; } if (errorMessage.isEmpty()) errorMessage = "Request Failed With Unknown Error."; Log.e(TAG, "isDoneToggle failed with error: " + errorMessage); } }); } /** * Formulates and sends REST request to the custom Node.js endpoint "<your_bluemix_route>/notifyAllDevices" deployed on Bluemix. * If configured correctly, expect an incoming push notification with the description of the completed item. * @param completedItem the task completed. */ private void notifyAllDevices(String completedItem) { Request request = new Request(bmsClient.getBluemixAppRoute() + "/notifyAllDevices", Request.POST); String json = "{\"text\":\"" + completedItem + "\"}"; HashMap headers = new HashMap(); List<String> contentType = new ArrayList<>(); contentType.add("application/json"); List<String> accept = new ArrayList<>(); accept.add("Application/json"); headers.put("Content-Type", contentType); headers.put("Accept", accept); request.setHeaders(headers); request.send(getApplicationContext(), json, new ResponseListener() { @Override public void onSuccess(Response response) { Log.i(TAG, "All registered devices notified successfully: " + response.getResponseText()); } // On failure, log errors @Override public void onFailure(Response response, Throwable throwable, JSONObject extendedInfo) { String errorMessage = ""; if (response != null) { errorMessage += response.toString() + "\n"; } if (throwable != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); throwable.printStackTrace(pw); errorMessage += "THROWN" + sw.toString() + "\n"; } if (extendedInfo != null) { errorMessage += "EXTENDED_INFO" + extendedInfo.toString() + "\n"; } if (errorMessage.isEmpty()) errorMessage = "Request Failed With Unknown Error."; Log.e(TAG, "notifyAllDevices failed with error: " + errorMessage); } }); } /** * If the device has been registered successfully, hold push notifications when the app is paused. * Also, clear list data when the app leaves the foreground. This forces successful authentication to see cloud data when the app re-enters the foreground. * Note: As soon as push.listen(notificationListener) is called again, the notifications will be released for consumption. */ @Override protected void onPause() { super.onPause(); Log.i(TAG, "Clearing list data since the app is leaving the foreground"); if (!mTodoItemAdapter.isEmpty()) { mTodoItemList.clear(); mTodoItemAdapter.notifyDataSetChanged(); } // Holds notifications. if (push != null) { push.hold(); } } }