Java tutorial
/* * Copyright (C) 2014 barter.li * * 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. */ package li.barter.fragments; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; import android.location.Location; import android.location.LocationManager; import android.os.Bundle; import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.content.Loader; import android.support.v4.view.MenuItemCompat; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v7.app.ActionBarActivity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MenuItem.OnActionExpandListener; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.GridView; import android.widget.SearchView; import android.widget.SearchView.OnCloseListener; import com.android.volley.Request.Method; import com.google.android.gms.analytics.HitBuilders.EventBuilder; import java.util.HashMap; import java.util.Map; import li.barter.R; import li.barter.activities.AbstractBarterLiActivity.AlertStyle; import li.barter.activities.AddOrEditBookActivity; import li.barter.activities.BookDetailActivity; import li.barter.activities.ScanIsbnActivity; import li.barter.activities.SearchBookPagerActivity; import li.barter.adapters.BooksGridAdapter; import li.barter.analytics.AnalyticsConstants.Actions; import li.barter.analytics.AnalyticsConstants.Categories; import li.barter.analytics.AnalyticsConstants.ParamKeys; import li.barter.analytics.AnalyticsConstants.ParamValues; import li.barter.analytics.AnalyticsConstants.Screens; import li.barter.analytics.GoogleAnalyticsManager; import li.barter.data.DBInterface; import li.barter.data.DBInterface.AsyncDbQueryCallback; import li.barter.data.DatabaseColumns; import li.barter.data.SQLiteLoader; import li.barter.data.TableSearchBooks; import li.barter.data.ViewSearchBooksWithLocations; import li.barter.fragments.dialogs.EnableLocationDialogFragment; import li.barter.fragments.dialogs.SingleChoiceDialogFragment; import li.barter.http.BlRequest; import li.barter.http.HttpConstants; import li.barter.http.HttpConstants.ApiEndpoints; import li.barter.http.HttpConstants.RequestId; import li.barter.http.IBlRequestContract; import li.barter.http.ResponseInfo; import li.barter.utils.AppConstants; import li.barter.utils.AppConstants.DeviceInfo; import li.barter.utils.AppConstants.FragmentTags; import li.barter.utils.AppConstants.Keys; import li.barter.utils.AppConstants.Loaders; import li.barter.utils.AppConstants.QueryTokens; import li.barter.utils.AppConstants.RequestCodes; import li.barter.utils.AppConstants.ResultCodes; import li.barter.utils.LoadMoreHelper; import li.barter.utils.LoadMoreHelper.LoadMoreCallbacks; import li.barter.utils.Logger; import li.barter.utils.SearchViewNetworkQueryHelper; import li.barter.utils.SearchViewNetworkQueryHelper.NetworkCallbacks; import li.barter.utils.SharedPreferenceHelper; import li.barter.utils.Utils; ; /** * @author Vinay S Shenoy Fragment for displaying Books Around Me. Also contains a Map that the user * can use to easily switch locations */ public class BooksAroundMeFragment extends AbstractBarterLiFragment implements LoaderCallbacks<Cursor>, AsyncDbQueryCallback, OnItemClickListener, LoadMoreCallbacks, NetworkCallbacks, OnCloseListener, OnActionExpandListener, OnClickListener, SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "BooksAroundMeFragment"; /** * Helper class for performing network search queries from Action Bar easily */ private SearchViewNetworkQueryHelper mSearchNetworkQueryHelper; /** * GridView into which the book content will be placed */ private GridView mBooksAroundMeGridView; /** * {@link BooksGridAdapter} instance for the Books */ private BooksGridAdapter mBooksAroundMeAdapter; /** * Current page used for load more */ private int mCurPage; /** * Used to remember the last location so that we can avoid fetching the books again if the last * fetched locations, and current fetched locations are close by */ private Location mLastFetchedLocation; /** * Flag to indicate whether a load operation is in progress */ private boolean mIsLoading; /** * Flag to indicate whether all items have been fetched */ private boolean mHasLoadedAllItems; /** * Reference to the Dialog Fragment for selecting the book add options */ private SingleChoiceDialogFragment mAddBookDialogFragment; /** * Action Bar SearchView */ private SearchView mSearchView; private View mEmptyView; private SwipeRefreshLayout mSwipeRefreshLayout; private EnableLocationDialogFragment mEnableLocationDialogFragment; @Override public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { init(container, savedInstanceState); setHasOptionsMenu(true); final View contentView = inflater.inflate(R.layout.fragment_books_around_me, container, false); setActionBarTitle(R.string.app_name); setUpRefreshLayout(contentView); mBooksAroundMeGridView = (GridView) contentView.findViewById(R.id.grid_books_around_me); LoadMoreHelper.init(this).on(mBooksAroundMeGridView); mBooksAroundMeAdapter = new BooksGridAdapter(getActivity(), true); mBooksAroundMeGridView.setAdapter(mBooksAroundMeAdapter); mBooksAroundMeGridView.setOnItemClickListener(this); mBooksAroundMeGridView.setVerticalScrollBarEnabled(false); mEmptyView = contentView.findViewById(R.id.empty_view); mEmptyView.findViewById(R.id.text_try_again).setOnClickListener(this); mEmptyView.findViewById(R.id.text_add_your_own).setOnClickListener(this); mEmptyView.findViewById(R.id.image_add_graphic).setOnClickListener(this); mCurPage = SharedPreferenceHelper.getInt(R.string.pref_last_fetched_page, 0); if (savedInstanceState != null) { mLastFetchedLocation = savedInstanceState.getParcelable(Keys.LAST_FETCHED_LOCATION); mHasLoadedAllItems = savedInstanceState.getBoolean(Keys.HAS_LOADED_ALL_ITEMS); } mAddBookDialogFragment = (SingleChoiceDialogFragment) getFragmentManager() .findFragmentByTag(FragmentTags.DIALOG_ADD_BOOK); mEnableLocationDialogFragment = (EnableLocationDialogFragment) getFragmentManager() .findFragmentByTag(FragmentTags.DIALOG_ENABLE_LOCATION); loadBookSearchResults(); return contentView; } private void setUpRefreshLayout(View view) { mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_container); mSwipeRefreshLayout.setOnRefreshListener(this); mSwipeRefreshLayout.setColorSchemeResources(R.color.apptheme_color, R.color.red, R.color.silver, R.color.apptheme_color); } @Override public void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable(Keys.LAST_FETCHED_LOCATION, mLastFetchedLocation); outState.putBoolean(Keys.HAS_LOADED_ALL_ITEMS, mHasLoadedAllItems); } /** * Starts the loader for book search results */ private void loadBookSearchResults() { getLoaderManager().restartLoader(Loaders.SEARCH_BOOKS, null, this); } /** * Method to fetch books around me from the server centered at a location, and in a search * radius * * @param center The {@link Location} representing the center */ private void fetchBooksAroundMe(final Location center) { if (center != null) { mIsLoading = true; final BlRequest request = new BlRequest(Method.GET, HttpConstants.getApiBaseUrl() + ApiEndpoints.SEARCH, null, mVolleyCallbacks); request.setRequestId(RequestId.SEARCH_BOOKS); final Map<String, String> params = new HashMap<String, String>(2); params.put(HttpConstants.LATITUDE, String.valueOf(center.getLatitude())); params.put(HttpConstants.LONGITUDE, String.valueOf(center.getLongitude())); Logger.d(TAG, center.getLatitude() + " " + center.getLongitude()); final int pageToFetch = mCurPage + 1; params.put(HttpConstants.PAGE, String.valueOf(pageToFetch)); params.put(HttpConstants.PERLIMIT, String.valueOf(AppConstants.DEFAULT_ITEM_COUNT)); request.addExtra(Keys.LOCATION, center); request.setParams(params); addRequestToQueue(request, true, 0, true); } } /** * Method to fetch books around me from the server centered at a location, and in a search * radius and the search field * * @param center The {@link Location} representing the center * @param radius The radius(in kilometers) to search in * @param bookname The book name to search for */ private void fetchBooksAroundMeForSearch(final Location center, final int radius, final String bookname) { if (center != null) { final BlRequest request = new BlRequest(Method.GET, HttpConstants.getApiBaseUrl() + ApiEndpoints.SEARCH, null, mVolleyCallbacks); request.setRequestId(RequestId.SEARCH_BOOKS); final Map<String, String> params = new HashMap<String, String>(2); params.put(HttpConstants.LATITUDE, String.valueOf(center.getLatitude())); params.put(HttpConstants.LONGITUDE, String.valueOf(center.getLongitude())); params.put(HttpConstants.SEARCH, bookname); params.put(HttpConstants.PERLIMIT, String.valueOf(AppConstants.DEFAULT_ITEM_COUNT)); request.addExtra(Keys.LOCATION, center); if (radius >= 1) { params.put(HttpConstants.RADIUS, String.valueOf(radius)); } request.setParams(params); addRequestToQueue(request, true, 0, true); } } @Override public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { inflater.inflate(R.menu.menu_books_around_me, menu); final MenuItem menuItem = menu.findItem(R.id.action_search); menuItem.setOnActionExpandListener(this); mSearchView = (SearchView) MenuItemCompat.getActionView(menuItem); mSearchNetworkQueryHelper = new SearchViewNetworkQueryHelper(mSearchView, this); mSearchNetworkQueryHelper.setSuggestCountThreshold(3); mSearchNetworkQueryHelper.setSuggestWaitThreshold(400); } @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.action_add_book: { showAddBookOptions(); return true; } default: { return super.onOptionsItemSelected(item); } } } /** * Show dialog for adding books */ private void showAddBookOptions() { mAddBookDialogFragment = new SingleChoiceDialogFragment(); mAddBookDialogFragment.show(AlertDialog.THEME_HOLO_LIGHT, R.array.add_book_choices, 0, R.string.add_book_dialog_head, getFragmentManager(), true, FragmentTags.DIALOG_ADD_BOOK); } @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { if (requestCode == RequestCodes.SCAN_ISBN && resultCode == ResultCodes.SUCCESS) { Bundle args = null; if (data != null) { args = data.getExtras(); } loadAddOrEditBookFragment(args); } else if (requestCode == RequestCodes.ADD_BOOK && resultCode == ActionBarActivity.RESULT_OK) { final String bookId = data.getStringExtra(DatabaseColumns.ID); final Intent bookDetailIntent = new Intent(getActivity(), BookDetailActivity.class); bookDetailIntent.putExtra(Keys.ID, bookId); startActivity(bookDetailIntent); } else { super.onActivityResult(requestCode, resultCode, data); } } @Override protected Object getTaskTag() { return hashCode(); } public void updateLocation(final Location location) { if ((location.getLatitude() == 0.0) && (location.getLongitude() == 0.0)) { return; } fetchBooksOnLocationUpdate(location); } /** * When the location is updated, check to see if books need to be refreshed, and refresh them * * @param location The location at which books should be fetched */ private void fetchBooksOnLocationUpdate(Location location) { if (shouldRefetchBooks(location)) { final Bundle cookie = new Bundle(1); cookie.putParcelable(Keys.LOCATION, location); // Delete the current search results before parsing the old ones DBInterface.deleteAsync(AppConstants.QueryTokens.DELETE_BOOKS_SEARCH_RESULTS, getTaskTag(), cookie, TableSearchBooks.NAME, null, null, true, this); } else { mCurPage = SharedPreferenceHelper.getInt(R.string.pref_last_fetched_page, 0); mHasLoadedAllItems = false; fetchBooksAroundMe(location); } } @Override public void onPause() { super.onPause(); if (mEnableLocationDialogFragment != null) { mEnableLocationDialogFragment.dismiss(); mEnableLocationDialogFragment = null; } saveLastFetchedInfoToPref(); } /** * Saves the last fetched info to shared preferences. This will be read again in onResume so as * to prevent refetching of the books */ private void saveLastFetchedInfoToPref() { SharedPreferenceHelper.set(R.string.pref_last_fetched_page, mCurPage); if (mLastFetchedLocation != null) { SharedPreferenceHelper.set(R.string.pref_last_fetched_latitude, mLastFetchedLocation.getLatitude()); SharedPreferenceHelper.set(R.string.pref_last_fetched_longitude, mLastFetchedLocation.getLongitude()); } } @Override public void onResume() { super.onResume(); //done to force refresh! just change the value accordingly, default value is false if (!SharedPreferenceHelper.getBoolean(R.string.pref_dont_refresh_books)) { SharedPreferenceHelper.set(R.string.pref_last_fetched_latitude, 0.0); SharedPreferenceHelper.set(R.string.pref_last_fetched_longitude, 0.0); SharedPreferenceHelper.set(R.string.pref_dont_refresh_books, true); } if (isLocationServiceEnabled()) { readLastFetchedInfoFromPref(); final Location latestLocation = DeviceInfo.INSTANCE.getLatestLocation(); if ((latestLocation.getLatitude() != 0.0) && (latestLocation.getLongitude() != 0.0)) { updateLocation(latestLocation); } } else { showEnableLocationDialog(); } } /** * Show the dialog for the user to add his name, in case it's not already added */ protected void showEnableLocationDialog() { mEnableLocationDialogFragment = new EnableLocationDialogFragment(); mEnableLocationDialogFragment.show(AlertDialog.THEME_HOLO_LIGHT, 0, R.string.enable_location, R.string.enable_location_message, R.string.enable, R.string.cancel, 0, getFragmentManager(), true, FragmentTags.DIALOG_ENABLE_LOCATION); } /** * Reads the latest fetched locations from shared preferences */ private void readLastFetchedInfoFromPref() { // Don't read from pref if already has fetched if (mLastFetchedLocation == null) { mLastFetchedLocation = new Location(LocationManager.PASSIVE_PROVIDER); Logger.d(TAG, mLastFetchedLocation.getLatitude() + ""); if (mLastFetchedLocation == null) { mLastFetchedLocation = new Location(LocationManager.NETWORK_PROVIDER); Logger.d(TAG, mLastFetchedLocation.getLatitude() + ""); } mLastFetchedLocation.setLatitude(SharedPreferenceHelper.getDouble(R.string.pref_last_fetched_latitude)); mLastFetchedLocation .setLongitude(SharedPreferenceHelper.getDouble(R.string.pref_last_fetched_longitude)); } } /** * Loads the Fragment to Add Or Edit Books * * @param bookInfo The book info to load. Can be <code>null</code>, in which case, it treats it * as Add a new book flow */ private void loadAddOrEditBookFragment(final Bundle bookInfo) { final Intent addBookIntent = new Intent(getActivity(), AddOrEditBookActivity.class); if (bookInfo != null) { addBookIntent.putExtras(bookInfo); } startActivityForResult(addBookIntent, RequestCodes.ADD_BOOK); } @Override public void onSuccess(final int requestId, final IBlRequestContract request, final ResponseInfo response) { if (requestId == RequestId.SEARCH_BOOKS) { mLastFetchedLocation = (Location) request.getExtras().get(Keys.LOCATION); mCurPage++; if (response.responseBundle.getBoolean(Keys.NO_BOOKS_FLAG_KEY)) { mHasLoadedAllItems = true; mCurPage--; } /* * Do nothing because the loader will take care of reloading the * data */ } } @Override public void onPostExecute(final IBlRequestContract request) { super.onPostExecute(request); if (request.getRequestId() == RequestId.SEARCH_BOOKS) { mIsLoading = false; hideRefreshing(); } } @Override public void onBadRequestError(final int requestId, final IBlRequestContract request, final int errorCode, final String errorMessage, final Bundle errorResponseBundle) { if (requestId == RequestId.SEARCH_BOOKS) { showCrouton(R.string.unable_to_fetch_books, AlertStyle.ERROR); } if (requestId == RequestId.SEARCH_BOOKS_FROM_EDITTEXT) { showCrouton(R.string.unable_to_fetch_books, AlertStyle.ERROR); } } @Override public void onOtherError(int requestId, IBlRequestContract request, int errorCode) { if (requestId == RequestId.SEARCH_BOOKS) { showCrouton(R.string.unable_to_fetch_books, AlertStyle.ERROR); } if (requestId == RequestId.SEARCH_BOOKS_FROM_EDITTEXT) { showCrouton(R.string.unable_to_fetch_books, AlertStyle.ERROR); } } @Override public Loader<Cursor> onCreateLoader(final int loaderId, final Bundle args) { if (loaderId == Loaders.SEARCH_BOOKS) { return new SQLiteLoader(getActivity(), false, ViewSearchBooksWithLocations.NAME, null, null, null, null, null, null, null); } else { return null; } } @Override public void onLoadFinished(final Loader<Cursor> loader, final Cursor cursor) { if (loader.getId() == Loaders.SEARCH_BOOKS) { if ((cursor.getCount() == 0)) { mBooksAroundMeGridView.setEmptyView(mEmptyView); } Logger.d(TAG, "Cursor Loaded with count: %d", cursor.getCount()); mBooksAroundMeAdapter.swapCursor(cursor); hideRefreshing(); } } @Override public void onLoaderReset(final Loader<Cursor> loader) { if (loader.getId() == Loaders.SEARCH_BOOKS) { mBooksAroundMeAdapter.swapCursor(null); } } /** * Checks if a new set of books should be fetched * * @param center The new center point at which the books should be fetched * @return <code>true</code> if a new set should be fetched, <code>false</code> otherwise */ private boolean shouldRefetchBooks(final Location center) { if (mLastFetchedLocation != null) { final float distanceBetweenCurAndLastFetchedLocations = Utils.distanceBetween(center, mLastFetchedLocation) / 1000; /* * If there's less than 25 km distance between the current location * and the location where we last fetched the books we don't need to * fetch the books again since the current set will include * those(The server uses 50 km as the search radius) */ if (distanceBetweenCurAndLastFetchedLocations <= 25.0f) { Logger.v(TAG, "Points are really close. Don't fetch"); return false; } } return true; } @Override public void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id) { if (parent.getId() == R.id.grid_books_around_me) { if (position == mBooksAroundMeAdapter.getCount() - 1) { showAddBookOptions(); } else { final Intent booksPagerIntent = new Intent(getActivity(), SearchBookPagerActivity.class); booksPagerIntent.putExtra(Keys.BOOK_POSITION, position); startActivity(booksPagerIntent); } } } @Override public void onInsertComplete(final int token, final Object cookie, final long insertRowId) { // TODO Auto-generated method stub } @Override public void onDeleteComplete(final int token, final Object cookie, final int deleteCount) { if (token == QueryTokens.DELETE_BOOKS_SEARCH_RESULTS) { assert (cookie != null); DBInterface.notifyChange(TableSearchBooks.NAME); mCurPage = 0; mHasLoadedAllItems = false; final Bundle args = (Bundle) cookie; fetchBooksAroundMe((Location) args.getParcelable(Keys.LOCATION)); } if (token == QueryTokens.DELETE_BOOKS_SEARCH_RESULTS_FROM_EDITTEXT) { assert (cookie != null); DBInterface.notifyChange(TableSearchBooks.NAME); final Bundle args = (Bundle) cookie; fetchBooksAroundMeForSearch((Location) args.getParcelable(Keys.LOCATION), 50, args.getString(Keys.SEARCH)); } } @Override public void onUpdateComplete(final int token, final Object cookie, final int updateCount) { // TODO Auto-generated method stub } @Override public void onQueryComplete(final int token, final Object cookie, final Cursor cursor) { // TODO Auto-generated method stub } @Override public void onLoadMore() { fetchBooksAroundMe(mLastFetchedLocation); } @Override public boolean isLoading() { return mIsLoading; } @Override public boolean hasLoadedAllItems() { return mHasLoadedAllItems; } @Override public boolean willHandleDialog(final DialogInterface dialog) { if ((mAddBookDialogFragment != null) && mAddBookDialogFragment.getDialog().equals(dialog)) { return true; } else if ((mEnableLocationDialogFragment != null) && mEnableLocationDialogFragment.getDialog().equals(dialog)) { return true; } return super.willHandleDialog(dialog); } @Override public void onDialogClick(final DialogInterface dialog, final int which) { if ((mAddBookDialogFragment != null) && mAddBookDialogFragment.getDialog().equals(dialog)) { if (which == 0) { // scan book startActivityForResult(new Intent(getActivity(), ScanIsbnActivity.class), RequestCodes.SCAN_ISBN); GoogleAnalyticsManager.getInstance().sendEvent( new EventBuilder(Categories.USAGE, Actions.ADD_BOOK).set(ParamKeys.TYPE, ParamValues.SCAN)); } else if (which == 1) { // add book manually loadAddOrEditBookFragment(null); GoogleAnalyticsManager.getInstance().sendEvent(new EventBuilder(Categories.USAGE, Actions.ADD_BOOK) .set(ParamKeys.TYPE, ParamValues.MANUAL)); } } else if ((mEnableLocationDialogFragment != null) && mEnableLocationDialogFragment.getDialog().equals(dialog)) { if (which == DialogInterface.BUTTON_POSITIVE) { // enable location Intent locationOptionsIntent = new Intent( android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS); startActivity(locationOptionsIntent); } else if (which == DialogInterface.BUTTON_NEGATIVE) { // cancel dialog.cancel(); } } else { super.onDialogClick(dialog, which); } } @Override public void performQuery(SearchView searchView, String query) { final Bundle cookie = new Bundle(2); cookie.putParcelable(Keys.LOCATION, mLastFetchedLocation); cookie.putString(Keys.SEARCH, query); // Delete the current search results before parsing the old ones DBInterface.deleteAsync(AppConstants.QueryTokens.DELETE_BOOKS_SEARCH_RESULTS_FROM_EDITTEXT, getTaskTag(), cookie, TableSearchBooks.NAME, null, null, false, BooksAroundMeFragment.this); } @Override public void onRefresh() { mSwipeRefreshLayout.setEnabled(false); reloadNearbyBooks(); } /** * Reloads nearby books */ private void reloadNearbyBooks() { mSearchView.setQuery(null, false); final Bundle cookie = new Bundle(2); cookie.putParcelable(Keys.LOCATION, mLastFetchedLocation); DBInterface.deleteAsync(AppConstants.QueryTokens.DELETE_BOOKS_SEARCH_RESULTS, getTaskTag(), cookie, TableSearchBooks.NAME, null, null, false, this); } private void showRefreshing() { //mSwipeRefreshLayout.setRefreshing(true); } private void hideRefreshing() { mSwipeRefreshLayout.setEnabled(true); mSwipeRefreshLayout.setRefreshing(false); } @Override public boolean onMenuItemActionCollapse(MenuItem item) { reloadNearbyBooks(); return true; } @Override public boolean onMenuItemActionExpand(MenuItem item) { return true; } @Override public boolean onClose() { return false; } @Override protected String getAnalyticsScreenName() { return Screens.BOOKS_AROUND_ME; } @Override public void onClick(View v) { final int id = v.getId(); switch (id) { case R.id.text_try_again: { reloadNearbyBooks(); break; } case R.id.image_add_graphic: case R.id.text_add_your_own: { showAddBookOptions(); break; } } } }