Google+ Integration - Updating a Coffee
Before we make a start at updating a coffee, here are the 2 lines of code necessary for adding a coffee (replacing our database insert call)
Base.app.coffeeList.add(c);
CoffeeApi.post("/coffees/" + Base.googleToken,c);
Unfortunately, this step isn't as simple and straightforward as the last step, in that we need to
display the coffee details (that the user has selected) on the Edit Screen via a GET request
send a PUT request to update our coffee on the server
return the user to the screen they were on before they chose to edit their coffee (NOT the Home Screen as with the 'Add' option)
So, the first thing to do (as I've made a few changes to the following) is replace your current CoffeeApi class with this one
package ie.cm.api;
import android.app.Fragment;
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.Log;
import android.widget.ImageView;
import com.android.volley.AuthFailureError;
import com.android.volley.Request;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageRequest;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.StringRequest;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import ie.cm.activities.Base;
import ie.cm.models.Coffee;
/**
* Created by ddrohan on 18/07/2016.
*/
public class CoffeeApi {
private static final String hostURL = "http://coffeemateweb.herokuapp.com";
private static final String LocalhostURL = "http://192.168.0.13:3000";
private static VolleyListener vListener;
private static final String TAG = "coffeemate";
public static void attachListener(VolleyListener fragment)
{
vListener = fragment;
}
public static void detachListener()
{
vListener = null;
}
private static void showDialog(String message) {
//Log.v(TAG, "Showing Dialog because " + Base.dialog.isShowing());
Base.dialog.setMessage(message);
if (!Base.dialog.isShowing())
Base.dialog.show();
}
private static void hideDialog() {
//Log.v(TAG, "Hiding Dialog because " + Base.dialog.isShowing());
if (Base.dialog.isShowing())
Base.dialog.dismiss();
}
///////////////////////////////////////////////////////////////////////////////////////////////
public static void getAll(String url, final SwipeRefreshLayout mSwipeRefreshLayout) {
Log.v(TAG, "GETing from " + url);
showDialog("Downloading Coffees...");
// Request a string response
StringRequest stringRequest = new StringRequest(Request.Method.GET, hostURL + url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Result handling
List<Coffee> result = null;
Type collectionType = new TypeToken<List<Coffee>>(){}.getType();
result = new Gson().fromJson(response, collectionType);
vListener.setList(result);
mSwipeRefreshLayout.setRefreshing(false);
hideDialog();
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Error handling
System.out.println("Something went wrong!");
mSwipeRefreshLayout.setRefreshing(false);
error.printStackTrace();
}
});
// Add the request to the queue
Base.app.add(stringRequest);
}
///////////////////////////////////////////////////////////////////////////////////////////////
public static void get(String url) {
Log.v(TAG, "GETing from " + url);
showDialog("Downloading a Coffee...");
// Request a string response
StringRequest stringRequest = new StringRequest(Request.Method.GET, hostURL + url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Result handling
Coffee result = null;
Type objType = new TypeToken<Coffee>(){}.getType();
result = new Gson().fromJson(response, objType);
vListener.setCoffee(result);
hideDialog();
//vListener.updateUI((Fragment)vListener);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Error handling
System.out.println("Something went wrong!");
error.printStackTrace();
}
});
// Add the request to the queue
Base.app.add(stringRequest);
}
///////////////////////////////////////////////////////////////////////////////////////////////
public static void post(String url,Coffee aCoffee) {
Log.v(TAG, "POSTing to : " + url);
showDialog("Adding a Coffee...");
Type objType = new TypeToken<Coffee>(){}.getType();
String json = new Gson().toJson(aCoffee, objType);
JSONObject jsonObject = null;
try {
jsonObject = new JSONObject(json);
} catch (JSONException e) {
e.printStackTrace();
}
JsonObjectRequest gsonRequest = new JsonObjectRequest( Request.Method.POST, hostURL + url,
jsonObject,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
hideDialog();
Log.v(TAG, "insert new Coffee " + response.toString());
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Handle Error
Log.v(TAG, "Unable to insert new Coffee");
}
}) {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "application/json; charset=utf-8");
//headers.put("User-agent", System.getProperty("http.agent"));
return headers;
}
};
// Add the request to the queue
Base.app.add(gsonRequest);
}
///////////////////////////////////////////////////////////////////////////////////////////////
public static void put(String url,Coffee aCoffee) {
Log.v(TAG, "PUTing to : " + url);
showDialog("Updating a Coffee...");
Type objType = new TypeToken<Coffee>(){}.getType();
String json = new Gson().toJson(aCoffee, objType);
JSONObject jsonObject = null;
try {
jsonObject = new JSONObject(json);
} catch (JSONException e) {
e.printStackTrace();
}
JsonObjectRequest gsonRequest = new JsonObjectRequest( Request.Method.PUT, hostURL + url,
jsonObject,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
// Result handling
Coffee result = null;
Type objType = new TypeToken<Coffee>(){}.getType();
try {
result = new Gson().fromJson(response.getString("data"), objType);
} catch (JSONException e) {
e.printStackTrace();
}
vListener.setCoffee(result);
hideDialog();
Log.v(TAG, "Updating a Coffee successful with :" + result);
}
},
new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Handle Error
Log.v(TAG, "Unable to update Coffee with error : " + error.getMessage());
}
}) {
@Override
public Map<String, String> getHeaders() throws AuthFailureError {
HashMap<String, String> headers = new HashMap<String, String>();
headers.put("Content-Type", "application/json; charset=utf-8");
//headers.put("User-agent", System.getProperty("http.agent"));
return headers;
}
};
// Add the request to the queue
Base.app.add(gsonRequest);
}
///////////////////////////////////////////////////////////////////////////////////////////////
public static void delete(String url) {
Log.v(TAG, "DELETEing from " + url);
// Request a string response
StringRequest stringRequest = new StringRequest(Request.Method.DELETE, hostURL + url,
new Response.Listener<String>() {
@Override
public void onResponse(String response) {
// Result handling
Log.v(TAG, "DELETE success " + response);
//vListener.updateUI((Fragment)vListener);
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
// Error handling
System.out.println("Something went wrong!");
error.printStackTrace();
}
});
// Add the request to the queue
Base.app.add(stringRequest);
}
///////////////////////////////////////////////////////////////////////////////////////////////
public static void getGooglePhoto(String url,final ImageView googlePhoto) {
ImageRequest imgRequest = new ImageRequest(url,
new Response.Listener<Bitmap>() {
@Override
public void onResponse(Bitmap response) {
Base.googlePhoto = response;
googlePhoto.setImageBitmap(Base.googlePhoto);
}
}, 0, 0, ImageView.ScaleType.FIT_XY, Bitmap.Config.ARGB_8888, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
System.out.println("Something went wrong!");
error.printStackTrace();
}
});
// Add the request to the queue
Base.app.add(imgRequest);
}
}
and your VolleyListener with this
public interface VolleyListener {
void setList(List list);
void setCoffee(Coffee c);
}
You'll get a few errors, but don't worry, we'll fix those now, so go ahead and open up your CoffeeFragment.java and replace your current class with this one
package ie.cm.fragments;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import java.util.List;
import ie.cm.R;
import ie.cm.activities.Base;
import ie.cm.adapters.CoffeeFilter;
import ie.cm.adapters.CoffeeListAdapter;
import ie.cm.api.CoffeeApi;
import ie.cm.api.VolleyListener;
import ie.cm.models.Coffee;
public class CoffeeFragment extends Fragment implements AdapterView.OnItemClickListener,
View.OnClickListener,
VolleyListener
{
protected static CoffeeListAdapter listAdapter;
protected ListView listView;
protected CoffeeFilter coffeeFilter;
public boolean favourites = false;
protected TextView titleBar;
protected SwipeRefreshLayout mSwipeRefreshLayout;
public CoffeeFragment() {
// Required empty public constructor
}
public static CoffeeFragment newInstance() {
CoffeeFragment fragment = new CoffeeFragment();
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = null;
v = inflater.inflate(R.layout.fragment_home, container, false);
listView = (ListView) v.findViewById(R.id.coffeeList);
mSwipeRefreshLayout = (SwipeRefreshLayout) v.findViewById(R.id.coffee_swipe_refresh_layout);
setSwipeRefreshLayout();
return v;
}
protected void setSwipeRefreshLayout()
{
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
CoffeeApi.getAll("/coffees/" + Base.googleToken,mSwipeRefreshLayout);
}
});
}
@Override
public void onResume() {
super.onResume();
//updateUI(this);
CoffeeApi.attachListener(this);
CoffeeApi.getAll("/coffees/" + Base.googleToken, mSwipeRefreshLayout);
}
@Override
public void onPause() {
super.onPause();
CoffeeApi.detachListener();
}
@Override
public void onStart()
{
super.onStart();
}
@Override
public void onClick(View view)
{
if (view.getTag() instanceof Coffee)
{
onCoffeeDelete ((Coffee) view.getTag());
}
}
public void onCoffeeDelete(final Coffee coffee)
{
String stringName = coffee.name;
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage("Are you sure you want to Delete the \'Coffee\' " + stringName + "?");
builder.setCancelable(false);
builder.setPositiveButton("Yes", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int id)
{
//Base.app.coffeeList.remove(coffee); // remove from our list
listAdapter.coffeeList.remove(coffee); // update adapters data
listAdapter.notifyDataSetChanged(); // refresh adapter
}
}).setNegativeButton("No", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int id)
{
dialog.cancel();
}
});
AlertDialog alert = builder.create();
alert.show();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Bundle activityInfo = new Bundle();
activityInfo.putString("coffeeID", (String)view.getTag());
FragmentTransaction ft = getFragmentManager().beginTransaction();
Fragment fragment = EditFragment.newInstance(activityInfo);
ft.replace(R.id.homeFrame, fragment);
ft.addToBackStack(null);
ft.commit();
}
@Override
public void setCoffee(Coffee c) {}
@Override
public void setList(List list) {
Base.app.coffeeList = list;
updateUI();
}
public void updateUI() {
//System.out.println("CALLING updateUI in CoffeeFragment");
titleBar = (TextView)getActivity().findViewById(R.id.recentAddedBarTextView);
titleBar.setText(R.string.recentlyViewedLbl);
((TextView)getActivity().findViewById(R.id.empty_list_view)).setText(R.string.recentlyViewedEmptyMessage);
listAdapter = new CoffeeListAdapter(getActivity(), this, Base.app.coffeeList);
listView.setAdapter (listAdapter);
coffeeFilter = new CoffeeFilter(Base.app.coffeeList,"all",listAdapter);
if (favourites) {
titleBar.setText(R.string.favouritesCoffeeLbl);
((TextView)getActivity().findViewById(R.id.empty_list_view)).setText(R.string.favouritesEmptyMessage);
coffeeFilter.setFilter("favourites"); // Set the filter text field from 'all' to 'favourites'
coffeeFilter.filter(null); // Filter the data, but don't use any prefix
}
listView.setOnItemClickListener(this);
listView.setEmptyView(getActivity().findViewById(R.id.empty_list_view));
listAdapter.notifyDataSetChanged(); // Update the adapter
}
}
I strongly recommend that you take some time and review this class and compare it to your previous class to understand the changes that have been made, particularly the introduction of the SwipeRefreshLayout and the refactoring of the onItemClick() method.
Now, open your EditFragment and ensure it implements our VolleyListener interface (and fix any errors). Then ensure your variable instances matches the following
TextView titleBar,titleName,titleShop;
Coffee aCoffee;
Boolean isFavourite;
EditText name, shop, price;
RatingBar ratingBar;
ImageView favouriteImage;
String coffeeID;
private OnFragmentInteractionListener mListener;
and your onCreate() method now should look like this
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(getArguments() != null) {
coffeeID = getArguments().getString("coffeeID");
}
}
As we need to reuse the widgets on our layout we need to refactor our onCreateView(), and using the variables you've just declared have a go at a revised version of the method, where you specifically bind to each widget on the layout (not just reference it).
Now, using the coffeeID from your onCreate() (above) see can you code correctly the APi call to retrieve a single coffee from the server? (All you need to do is append the coffee id to the call made to get all coffees).
Before you get to actually update the coffee on the server, it's probably worth testing your app at this point to see if everything is working correctly, so run your app and confirm you get to see the coffee details on the Edit Screen once the user has selected a particular coffee to edit, like so
and if the user selects "Regular Joe"
You're probably not seeing anything (or maybe your app even crashed?) and this is (probably) because you haven't implemented the interface methods correctly (or at all!) and you haven't allowed for updating the UI with the coffee details AFTER the individual coffee details have been retrieved.
We'll have a look at the solution in the next step but try and complete it yourself first by referring to how we achieved our goal in our CoffeeFragment class.