I was asked to create a test APK file by a company HR when I applied for an Android developers job,
Specification:
The goal is to develop a simple application which finds the local restaurants. The application will contain 2 screens:
A list of nearby restaurants within 1 mile radius A map that shows the restaurants and their location The application should be able to work in an offline mode showing the latest places found so far (even if these are outside the 1 mile radius). In addition, the user must have an option to refresh the data regardless of the screen he currently sees.
In order to get restaurant information please use the Google Places API.
I put in my best effort and created the app and when I emailed them the source code, I was given the following feedback:
The team felt you didn't have enough knowledge about Android in general - you use some slow ways to send data inside the application, don't know the design guidelines for Android and provided an application that looks like an IOS clone. We are not asking people to do beautiful applications but implementing things that Google says never do on Android is certainly a negative point during review.
I think that the above comments are a bit rude and unreasonable. Could an experienced Android developer PLEASE review my code and let me know if these comments are true and where have I done wrong!
This has completely ruined my day, and I am feeling very depressed that my code and knowledge of Android has been rated that bad.
RestaurantsActivity.java
RestaurantsActivity extends FragmentActivity implements RestaurantListFragment.Contract { private final static String TAG = RestaurantsActivity.class.getSimpleName(); private static final String TAG_LEFT_FRAG = "info_headers"; private RestaurantListFragment left_frag = null; private DatabaseHelper mDBHelper = null; private Boolean isInternetPresent = false; private ConnectionDetector connectionDetector; GooglePlaces googlePlaces; GPSTracker gpsTracker; ProgressDialog pDialog; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().requestFeature(Window.FEATURE_ACTION_BAR); setContentView(R.layout.info_sub_system_main); ActionBar actionBar = getActionBar(); actionBar.setDisplayShowTitleEnabled(true); actionBar.setDisplayHomeAsUpEnabled(false); mDBHelper = new DatabaseHelper(RestaurantsActivity.this); connectionDetector = new ConnectionDetector(getApplicationContext()); // Check if Internet present isInternetPresent = connectionDetector.isConnectingToInternet(); if (!isInternetPresent) { Toast.makeText(this, "Internet connection unavailable", Toast.LENGTH_SHORT).show(); } gpsTracker = new GPSTracker(this); if (gpsTracker.canGetLocation()) { Log.d("Your Location", "latitude:" + gpsTracker.getLatitude() + ", longitude: " + gpsTracker.getLongitude()); new LoadPlaces().execute(); } else { Toast.makeText( RestaurantsActivity.this, "Couldn't get location information. Please enable GPS \nWill use cached data from previous search if available", Toast.LENGTH_LONG).show(); } left_frag = (RestaurantListFragment)getSupportFragmentManager().findFragmentByTag( TAG_LEFT_FRAG); if (left_frag == null) { left_frag = new RestaurantListFragment(); getSupportFragmentManager().beginTransaction() .add(R.id.info_header_mainfrag, left_frag, TAG_LEFT_FRAG).commit(); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } @Override public void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); } @Override public void onRestaurantSelected(Place place) { if (place != null) { Intent detailsIntent = new Intent(getApplicationContext(), RestaurantDetailActivity.class); detailsIntent.putExtra("reference", place.getRef()); detailsIntent.putExtra("name", place.getName()); detailsIntent.putExtra("formatted_address", place.getFormattedAddress()); detailsIntent.putExtra("formatted_phone_number", place.getPhone()); detailsIntent.putExtra("lat", place.getLatitudeAsString()); detailsIntent.putExtra("lng", place.getLongitudeAsString()); startActivity(detailsIntent); } } @Override public boolean isPersistentSelection() { return false; } @Override public void onDestroy() { super.onDestroy(); mDBHelper.close(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.ab_map: startActivity(new Intent(RestaurantsActivity.this, ShowPlacesOnMapActivity.class)); return true; case R.id.ab_search: new LoadPlaces().execute(); return true; } return false; } class LoadPlaces extends AsyncTask<String, String, Boolean> { @Override protected void onPreExecute() { super.onPreExecute(); pDialog = new ProgressDialog(RestaurantsActivity.this); pDialog.setMessage(Html.fromHtml("<b>Search</b><br/>Loading Places...")); pDialog.setIndeterminate(false); pDialog.setCancelable(false); pDialog.show(); } @Override protected Boolean doInBackground(String... args) { // creating Places class object googlePlaces = new GooglePlaces(); try { String types = "cafe|restaurant"; double radius = 1600; // 1 mile (in meters) List<Place> listPlace = googlePlaces.search(gpsTracker.getLatitude(), gpsTracker.getLongitude(), radius, types); if (listPlace != null && listPlace.size() > 0) { for (Place p : listPlace) { Place place = googlePlaces.getPlaceDetails(p.getRef()); ContentValues cv = new ContentValues(); cv.put(DatabaseHelper.UNIQUEID, place.getId()); cv.put(DatabaseHelper.REFERENCE, place.getRef()); cv.put(DatabaseHelper.NAME, place.getName()); cv.put(DatabaseHelper.ADDRESS, place.getFormattedAddress()); cv.put(DatabaseHelper.PHONE, place.getPhone()); cv.put(DatabaseHelper.LAT, place.getLatitude()); cv.put(DatabaseHelper.LNG, place.getLongitude()); mDBHelper.getWritableDatabase().replace(DatabaseHelper.TABLE, null, cv); } } return true; } catch (Exception e) { Log.d(TAG, e.toString()); } return false; } @Override protected void onPostExecute(Boolean success) { pDialog.dismiss(); if (success) { if (left_frag != null && left_frag.isVisible()) { Log.d(TAG, "Calling fillListView after FETCHING"); left_frag.fillListView(); } } else { Toast.makeText(RestaurantsActivity.this, "Sorry error occured.", Toast.LENGTH_SHORT) .show(); } } } RestaurantListFragment.java
public class RestaurantListFragment extends ContractListFragment<RestaurantListFragment.Contract> { static private final String STATE_CHECKED = "com.example.restaurantsnearby.STATE_CHECKED"; private PlacesAdapter adapter = null; private String TAG = RestaurantListFragment.class.getSimpleName(); @Override public void onActivityCreated(Bundle state) { super.onActivityCreated(state); setHasOptionsMenu(true); getListView().setSelector(R.drawable.list_selector); getListView().setDrawSelectorOnTop(false); fillListView(); if (state != null) { int position = state.getInt(STATE_CHECKED, -1); Log.d(TAG + "List View Item Position = ", String.valueOf(position)); if (position > -1) { getListView().setItemChecked(position, true); } } } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.info_header_listview, container, false); return view; } @Override public void onDestroyView() { super.onDestroyView(); setListAdapter(null); } @Override public void onResume() { Log.d(TAG, "onResume Called"); super.onResume(); //fillListView(); } public void fillListView() { Log.d(TAG, "fillListView Called"); if (getListView().getAdapter() == null) { Log.d(TAG, "SETTING ADAPTER"); adapter = new PlacesAdapter(fetchFromDB()); setListAdapter(adapter); } else { Log.d(TAG, "REFILLING ADAPTER"); adapter.refill(fetchFromDB()); } } @Override public void onListItemClick(ListView l, View v, int position, long id) { if (getContract().isPersistentSelection()) { getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); l.setItemChecked(position, true); } else { getListView().setChoiceMode(ListView.CHOICE_MODE_NONE); } Log.d(TAG, ((Place) this.getListAdapter().getItem(position-1)).getRef()); getContract().onRestaurantSelected(((Place) this.getListAdapter().getItem(position-1))); } @Override public void onSaveInstanceState(Bundle state) { super.onSaveInstanceState(state); if (getView() != null) { state.putInt(STATE_CHECKED, getListView().getCheckedItemPosition()); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.ab_view, menu); } private List<Place> fetchFromDB() { Cursor cursor = null; List<Place> list = new ArrayList<Place>(); try { DatabaseHelper dbHelper = new DatabaseHelper(getActivity()); cursor = dbHelper.getReadableDatabase().rawQuery( "SELECT * FROM " + DatabaseHelper.TABLE, null); if (cursor != null && cursor.moveToFirst()) { do { Place place = new Place(); place.setId(cursor.getString(cursor.getColumnIndex(DatabaseHelper.UNIQUEID))); place.setRef(cursor.getString(cursor.getColumnIndex(DatabaseHelper.REFERENCE))); place.setName(cursor.getString(cursor.getColumnIndex(DatabaseHelper.NAME))); place.setFormattedAddress(cursor.getString(cursor.getColumnIndex(DatabaseHelper.ADDRESS))); place.setPhone(cursor.getString(cursor.getColumnIndex(DatabaseHelper.PHONE))); place.setLatitude(cursor.isNull(cursor.getColumnIndex(DatabaseHelper.LAT)) ? 0 : cursor.getDouble(cursor.getColumnIndex(DatabaseHelper.LAT))); place.setLongitude(cursor.isNull(cursor.getColumnIndex(DatabaseHelper.LNG)) ? 0 : cursor.getDouble(cursor.getColumnIndex(DatabaseHelper.LNG))); list.add(place); } while (cursor.moveToNext()); } } catch (Exception e) { Log.d(TAG, e.toString()); } finally { if (cursor != null && !cursor.isClosed()) { cursor.close(); } } return list; } // LIST ADAPTER BEGIN class PlacesAdapter extends ArrayAdapter<Place> { PlacesAdapter(List<Place> placesList) { super(getActivity(), R.layout.info_header_row, R.id.restaurant_name, placesList); Log.d(TAG + " PlacesAdapter List Size = ", String.valueOf(placesList.size())); } @Override public View getView(int position, View convertView, ViewGroup parent) { PlacesViewHolder wrapper = null; if (convertView == null) { convertView = LayoutInflater.from(getActivity()).inflate(R.layout.info_header_row, null); wrapper = new PlacesViewHolder(convertView, getActivity()); convertView.setTag(wrapper); } else { wrapper = (PlacesViewHolder)convertView.getTag(); } wrapper.populateFrom(getItem(position)); return (convertView); } @TargetApi(11) public void refill(List<Place> placesList) { adapter.clear(); if (placesList != null) { Log.d(TAG + "REFILL ADAPTER List Size = ", String.valueOf(placesList.size())); // If the platform supports it, use addAll if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { adapter.addAll(placesList); } else { for (Place place : placesList) { adapter.add(place); } } } else{ Log.d(TAG , "PLACES LIST IS NULL"); } notifyDataSetChanged(); Log.d("notifyDataSetChanged();", "CALLED"); } } interface Contract { void onRestaurantSelected(Place place); boolean isPersistentSelection(); } } ShowPlacesOnMapActivity.java
public class ShowPlacesOnMapActivity extends Activity { // Google Map private GoogleMap googleMap; private DatabaseHelper mDBHelper = null; @SuppressWarnings("unused") private Boolean isInternetPresent = false; private ConnectionDetector connectionDetector; GooglePlaces googlePlaces; GPSTracker gpsTracker; ProgressDialog pDialog; private final String TAG = ShowPlacesOnMapActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().requestFeature(Window.FEATURE_ACTION_BAR); setContentView(R.layout.map_places); ActionBar actionBar = getActionBar(); actionBar.setDisplayShowTitleEnabled(true); actionBar.setDisplayHomeAsUpEnabled(false); mDBHelper = new DatabaseHelper(ShowPlacesOnMapActivity.this); connectionDetector = new ConnectionDetector(getApplicationContext()); isInternetPresent = connectionDetector.isConnectingToInternet(); gpsTracker = new GPSTracker(this); try { initilizeMap(); googleMap.setMapType(GoogleMap.MAP_TYPE_NORMAL); googleMap.setMyLocationEnabled(true); googleMap.getUiSettings().setZoomControlsEnabled(true); googleMap.getUiSettings().setMyLocationButtonEnabled(true); googleMap.getUiSettings().setCompassEnabled(true); googleMap.getUiSettings().setRotateGesturesEnabled(true); googleMap.getUiSettings().setZoomGesturesEnabled(true); List<Place> placeList = fetchFromDB(); if (placeList.size() <= 0) { finish(); } Double lastLat = 0.0; Double lastLng = 0.0; for (Place place : placeList) { lastLat = place.getLatitude(); lastLng = place.getLongitude(); if (lastLat + lastLng != 0) { MarkerOptions marker = new MarkerOptions() .position(new LatLng(lastLat, lastLng)).title(place.getName()) .snippet(place.getFormattedAddress()); marker.icon(BitmapDescriptorFactory .defaultMarker(BitmapDescriptorFactory.HUE_BLUE)); googleMap.addMarker(marker); } } CameraPosition cameraPosition = new CameraPosition.Builder() .target(new LatLng(lastLat, lastLng)).zoom(15).build(); googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)); } catch (Exception e) { Log.d(TAG, e.toString()); } } @Override protected void onResume() { super.onResume(); initilizeMap(); } @SuppressLint("NewApi") private void initilizeMap() { if (googleMap == null) { googleMap = ((MapFragment)getFragmentManager().findFragmentById(R.id.map)).getMap(); // check if map is created successfully or not if (googleMap == null) { Toast.makeText(getApplicationContext(), "Sorry! unable to create maps", Toast.LENGTH_SHORT).show(); } } } @Override public boolean onCreateOptionsMenu(Menu menu) { new MenuInflater(this).inflate(R.menu.ab_view_map, menu); return (super.onCreateOptionsMenu(menu)); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.ab_search: // check if GPS location can get if (gpsTracker.canGetLocation()) { Log.d("Your Location", "latitude:" + gpsTracker.getLatitude() + ", longitude: " + gpsTracker.getLongitude()); new LoadPlaces().execute(); } else { // Can't get user's current location Toast.makeText( ShowPlacesOnMapActivity.this, "Couldn't get location information. Please enable GPS \nWill use cached data from previous search if available", Toast.LENGTH_LONG).show(); } return true; } return false; } private List<Place> fetchFromDB() { Cursor cursor = null; List<Place> list = new ArrayList<Place>(); try { DatabaseHelper dbHelper = new DatabaseHelper(ShowPlacesOnMapActivity.this); cursor = dbHelper.getReadableDatabase().rawQuery( "SELECT * FROM " + DatabaseHelper.TABLE, null); if (cursor != null && cursor.moveToFirst()) { do { Place place = new Place(); place.setId(cursor.getString(cursor.getColumnIndex(DatabaseHelper.UNIQUEID))); place.setRef(cursor.getString(cursor.getColumnIndex(DatabaseHelper.REFERENCE))); place.setName(cursor.getString(cursor.getColumnIndex(DatabaseHelper.NAME))); place.setFormattedAddress(cursor.getString(cursor .getColumnIndex(DatabaseHelper.ADDRESS))); place.setPhone(cursor.getString(cursor.getColumnIndex(DatabaseHelper.PHONE))); place.setLatitude(cursor.isNull(cursor.getColumnIndex(DatabaseHelper.LAT)) ? 0 : cursor.getDouble(cursor.getColumnIndex(DatabaseHelper.LAT))); place.setLongitude(cursor.isNull(cursor.getColumnIndex(DatabaseHelper.LNG)) ? 0 : cursor.getDouble(cursor.getColumnIndex(DatabaseHelper.LNG))); list.add(place); } while (cursor.moveToNext()); } } catch (Exception e) { Log.d(TAG, e.toString()); } finally { if (cursor != null && !cursor.isClosed()) { cursor.close(); } } return list; } @Override public void onDestroy() { super.onDestroy(); mDBHelper.close(); } class LoadPlaces extends AsyncTask<String, String, Boolean> { @Override protected void onPreExecute() { super.onPreExecute(); pDialog = new ProgressDialog(ShowPlacesOnMapActivity.this); pDialog.setMessage(Html.fromHtml("<b>Search</b><br/>Loading Places...")); pDialog.setIndeterminate(false); pDialog.setCancelable(false); pDialog.show(); } /** * getting Places JSON */ @Override protected Boolean doInBackground(String... args) { // creating Places class object googlePlaces = new GooglePlaces(); try { String types = "cafe|restaurant"; // Listing places only cafes, double radius = 1600; // 1 mile (in meters) // get nearest places List<Place> listPlace = googlePlaces.search(gpsTracker.getLatitude(), gpsTracker.getLongitude(), radius, types); if (listPlace != null && listPlace.size() > 0) { for (Place p : listPlace) { Place place = googlePlaces.getPlaceDetails(p.getRef()); ContentValues cv = new ContentValues(); cv.put(DatabaseHelper.UNIQUEID, place.getId()); cv.put(DatabaseHelper.REFERENCE, place.getRef()); cv.put(DatabaseHelper.NAME, place.getName()); cv.put(DatabaseHelper.ADDRESS, place.getFormattedAddress()); cv.put(DatabaseHelper.PHONE, place.getPhone()); cv.put(DatabaseHelper.LAT, place.getLatitude()); cv.put(DatabaseHelper.LNG, place.getLongitude()); mDBHelper.getWritableDatabase().replace(DatabaseHelper.TABLE, null, cv); } } return true; } catch (Exception e) { Log.d(TAG, e.toString()); } return false; } @Override protected void onPostExecute(Boolean success) { pDialog.dismiss(); if (success) { List<Place> placeList = fetchFromDB(); if (placeList.size() <= 0) { finish(); } Double lastLat = 0.0; Double lastLng = 0.0; for (Place place : placeList) { lastLat = place.getLatitude(); lastLng = place.getLongitude(); if (lastLat + lastLng != 0) { MarkerOptions marker = new MarkerOptions() .position(new LatLng(lastLat, lastLng)).title(place.getName()) .snippet(place.getFormattedAddress()); marker.icon(BitmapDescriptorFactory .defaultMarker(BitmapDescriptorFactory.HUE_BLUE)); googleMap.addMarker(marker); } } CameraPosition cameraPosition = new CameraPosition.Builder() .target(new LatLng(lastLat, lastLng)).zoom(15).build(); googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition)); } else { Toast.makeText(ShowPlacesOnMapActivity.this, "Sorry error occured.", Toast.LENGTH_SHORT).show(); } } } } GooglePlaces.java
public class GooglePlaces { // Google API Key //private static final String API_KEY = "MY GOOGLE KEY GOES HERE"; // Android private static final String API_KEY = "MY GOOGLE KEY GOES HERE"; // Browser private static final String TAG = GooglePlaces.class.getSimpleName(); // Google Places serach url's private static final String PLACES_SEARCH_URL = "https://maps.googleapis.com/maps/api/place/search/json?"; private static final String PLACES_DETAILS_URL = "https://maps.googleapis.com/maps/api/place/details/json?"; @SuppressWarnings("unused") private double _latitude; @SuppressWarnings("unused") private double _longitude; private double _radius; /** * Searching places * @param latitude - latitude of place * @params longitude - longitude of place * @param radius - radius of searchable area * @param types - type of place to search * @return list of places * */ public List<Place> search(double latitude, double longitude, double radius, String types) throws Exception { this._latitude = latitude; this._longitude = longitude; this._radius = radius; StringBuilder urlString = new StringBuilder( PLACES_SEARCH_URL); urlString.append("&location="); urlString.append(Double.toString(latitude)); urlString.append(","); urlString.append(Double.toString(longitude)); urlString.append("&radius=" + _radius); urlString.append("&types=" + types); urlString.append("&sensor=false&key=" + API_KEY); Log.d(TAG, urlString.toString()); try { String json = getJSON(urlString.toString()); Log.d(TAG, json); JSONObject object = new JSONObject(json); JSONArray array = object.getJSONArray("results"); List<Place> arrayList = new ArrayList<Place>(); for (int i = 0; i < array.length(); i++) { try { Place place = Place .jsonToPontoReferencia((JSONObject) array.get(i),false); Log.d("Places Services ", "" + place); arrayList.add(place); } catch (Exception e) { Log.d(TAG, e.toString()); } } return arrayList; } catch (JSONException ex) { Logger.getLogger(GooglePlaces.class.getName()).log(Level.SEVERE, null, ex); } return null; } /** * Searching single place full details * @param refrence - reference id of place * - which you will get in search api request * */ public Place getPlaceDetails(String reference) throws Exception { StringBuilder urlString = new StringBuilder( PLACES_DETAILS_URL); urlString.append("&reference=" + reference); urlString.append("&sensor=false&key=" + API_KEY); Log.d(TAG, urlString.toString()); Place place = new Place(); try { String json = getJSON(urlString.toString()); JSONObject object = new JSONObject(json); JSONObject result=object.getJSONObject("result"); place = Place.jsonToPontoReferencia(result, true); return place; } catch (JSONException ex) { Logger.getLogger(GooglePlaces.class.getName()).log(Level.SEVERE, null, ex); } return null; } protected String getJSON(String url) { return getUrlContents(url); } private String getUrlContents(String theUrl) { StringBuilder content = new StringBuilder(); try { URL url = new URL(theUrl); URLConnection urlConnection = url.openConnection(); BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(urlConnection.getInputStream()), 8); String line; while ((line = bufferedReader.readLine()) != null) { content.append(line + "\n"); } bufferedReader.close(); }catch (Exception e) { e.printStackTrace(); } return content.toString(); } } The other classes are for checking data connectivity and gps.