Java tutorial
/* * Copyright (C) 2013 Digipom Inc. * * 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 com.digipom.manteresting.android.fragment; import static com.digipom.manteresting.android.config.AppConfig.MANTERESTING_SERVER; import static com.digipom.manteresting.android.service.rest.RestServiceHelper.EXTRA_OFFSET; import static com.digipom.manteresting.android.service.rest.RestServiceHelper.EXTRA_REQUEST_ID; import static com.digipom.manteresting.android.service.rest.RestServiceHelper.STATUS_ERROR; import static com.digipom.manteresting.android.service.rest.RestServiceHelper.STATUS_RUNNING; import static com.digipom.manteresting.android.service.rest.RestServiceHelper.STATUS_SUCCESS; import org.json.JSONObject; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Intent; import android.content.ServiceConnection; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.ResultReceiver; import android.support.v4.app.LoaderManager; import android.support.v4.content.CursorLoader; import android.support.v4.content.Loader; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.Button; import android.widget.ListView; import com.actionbarsherlock.app.SherlockListFragment; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.digipom.manteresting.android.R; import com.digipom.manteresting.android.adapter.NailCursorAdapter; import com.digipom.manteresting.android.config.LoggerConfig; import com.digipom.manteresting.android.connector.ServiceConnector; import com.digipom.manteresting.android.provider.ManterestingContract; import com.digipom.manteresting.android.provider.ManterestingContract.Nails; import com.digipom.manteresting.android.service.cache.CacheService; import com.digipom.manteresting.android.service.rest.RestServiceHelper; public class NailFragment extends SherlockListFragment implements LoaderManager.LoaderCallbacks<Cursor>, ServiceConnection { private static final int MAX_COUNT = 500; private static final int DEFAULT_LIMIT = 50; private static final String TAG = "NailFragment"; private static final int LOADER_ID = 1; private final ResultReceiver resultReceiver = new ResultReceiver(new Handler()) { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { if (resultCode == STATUS_SUCCESS) { if (resultCode == STATUS_SUCCESS && resultData.getInt(EXTRA_REQUEST_ID) == offsetRequestId) { offsetForNextPage = resultData.getInt(EXTRA_OFFSET); } } updateUiState(resultCode); } }; private ServiceConnector<CacheService> serviceConnector; private NailCursorAdapter nailAdapter; private ListView listView; private View connectingEmpty; private View couldNotConnectEmpty; private View headerMoreItemsLoadingIndicator; private View headerLoadMoreItemsIndicator; private View headerCouldNotLoadMoreItemsIndicator; private View footerView; private View footerMoreItemsLoadingIndicator; private View footerCouldNotLoadMoreItemsIndicator; private int offsetForNextPage = -1; private int offsetRequestId = -1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View layout = inflater.inflate(R.layout.nail_fragment, container, false); connectingEmpty = layout.findViewById(R.id.connectingEmpty); couldNotConnectEmpty = layout.findViewById(R.id.couldNotConnectEmpty); listView = (ListView) layout.findViewById(android.R.id.list); final View headerView = inflater.inflate(R.layout.nail_header_placeholder, null); headerMoreItemsLoadingIndicator = headerView.findViewById(R.id.moreItemsLoadingIndicator); headerLoadMoreItemsIndicator = headerView.findViewById(R.id.loadMoreItemsIndicator); headerCouldNotLoadMoreItemsIndicator = headerView.findViewById(R.id.couldNotLoadMoreItemsIndicator); final OnClickListener fetchNewNailsOnClickListener = new OnClickListener() { @Override public void onClick(View v) { if (getActivity() != null) { doSync(); } } }; ((Button) headerView.findViewById(R.id.fetchNewNails)).setOnClickListener(fetchNewNailsOnClickListener); ((Button) headerView.findViewById(R.id.retryConnect)).setOnClickListener(fetchNewNailsOnClickListener); listView.addHeaderView(headerView); footerView = inflater.inflate(R.layout.nail_paging_placeholder, null); footerMoreItemsLoadingIndicator = footerView.findViewById(R.id.moreItemsLoadingIndicator); footerCouldNotLoadMoreItemsIndicator = footerView.findViewById(R.id.couldNotLoadMoreItemsIndicator); ((Button) footerView.findViewById(R.id.retryConnect)).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (getActivity() != null) { if (offsetForNextPage == -1) { offsetForNextPage = calculateInitialOffsetForNextFetch(); } if (offsetForNextPage != -1) { doSyncForPageOffset(offsetForNextPage); } } } }); listView.addFooterView(footerView); listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // Ignore } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { if (totalItemCount > 2 && getActivity() != null && footerView != null && footerMoreItemsLoadingIndicator != null && footerView.getVisibility() == View.VISIBLE && footerMoreItemsLoadingIndicator.getVisibility() == View.VISIBLE && firstVisibleItem + visibleItemCount >= totalItemCount) { if (offsetForNextPage == -1) { offsetForNextPage = calculateInitialOffsetForNextFetch(); } if (offsetForNextPage != -1) { doSyncForPageOffset(offsetForNextPage); } } } }); ((Button) layout.findViewById(R.id.retryConnect)).setOnClickListener(fetchNewNailsOnClickListener); return layout; } private int calculateInitialOffsetForNextFetch() { int offsetToReturn = -1; if (listView != null) { try { final int listViewCount = listView.getCount(); if (listViewCount > 2) { // Get the first and last nail IDs, and subtract the 2 // to get the initial offset. final Cursor firstCursor = (Cursor) listView.getItemAtPosition(1); if (firstCursor != null && !firstCursor.isClosed()) { final int columnIndex = firstCursor.getColumnIndex(Nails.NAIL_ID); final int firstNailId = Integer.parseInt(firstCursor.getString(columnIndex)); final Cursor lastCursor = (Cursor) listView.getItemAtPosition(listViewCount - 2); if (lastCursor != null && !lastCursor.isClosed()) { final int lastNailId = Integer.parseInt(firstCursor.getString(columnIndex)); offsetForNextPage = firstNailId - lastNailId; return firstNailId - lastNailId; } } } } catch (Exception e) { if (LoggerConfig.canLog(Log.ERROR)) { Log.e(TAG, "getOffsetForNextFetch()", e); } } } return offsetToReturn; } private void doSync() { RestServiceHelper.syncNails(getActivity(), resultReceiver, 0, DEFAULT_LIMIT); updateUiState(STATUS_RUNNING); } private void doSyncForPageOffset(int offset) { offsetRequestId = RestServiceHelper.syncNails(getActivity(), resultReceiver, offset, DEFAULT_LIMIT); updateUiState(STATUS_RUNNING); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); serviceConnector = new ServiceConnector<CacheService>(CacheService.class, getActivity(), this); serviceConnector.startAndBindService(); nailAdapter = new NailCursorAdapter(this.getActivity().getApplicationContext(), serviceConnector); setListAdapter(nailAdapter); registerForContextMenu(listView); if (LoggerConfig.canLog(Log.VERBOSE)) { LoaderManager.enableDebugLogging(true); } getLoaderManager().initLoader(LOADER_ID, null, this); // This is a little bit heavy, but means we'll be doing a sync each // time we initialize the fragment. doSync(); } private void updateUiState(int nailFetchState) { if (connectingEmpty != null && couldNotConnectEmpty != null) { if (nailFetchState == STATUS_RUNNING || nailFetchState == STATUS_SUCCESS) { connectingEmpty.setVisibility(View.VISIBLE); couldNotConnectEmpty.setVisibility(View.GONE); } else { connectingEmpty.setVisibility(View.GONE); couldNotConnectEmpty.setVisibility(View.VISIBLE); } } if (headerMoreItemsLoadingIndicator != null && headerLoadMoreItemsIndicator != null && headerCouldNotLoadMoreItemsIndicator != null) { if (nailFetchState == STATUS_RUNNING) { headerMoreItemsLoadingIndicator.setVisibility(View.VISIBLE); headerLoadMoreItemsIndicator.setVisibility(View.INVISIBLE); headerCouldNotLoadMoreItemsIndicator.setVisibility(View.INVISIBLE); } else if (nailFetchState == STATUS_ERROR) { headerMoreItemsLoadingIndicator.setVisibility(View.INVISIBLE); headerLoadMoreItemsIndicator.setVisibility(View.INVISIBLE); headerCouldNotLoadMoreItemsIndicator.setVisibility(View.VISIBLE); } else if (nailFetchState == STATUS_SUCCESS) { headerMoreItemsLoadingIndicator.setVisibility(View.INVISIBLE); headerLoadMoreItemsIndicator.setVisibility(View.VISIBLE); headerCouldNotLoadMoreItemsIndicator.setVisibility(View.INVISIBLE); } } if (listView != null && footerView != null && footerMoreItemsLoadingIndicator != null && footerCouldNotLoadMoreItemsIndicator != null) { // Maybe should be 502 instead, to account for the header/footer. if (listView.getCount() >= MAX_COUNT) { footerView.setVisibility(View.GONE); } else { footerView.setVisibility(View.VISIBLE); } if (nailFetchState == STATUS_RUNNING || nailFetchState == STATUS_SUCCESS) { footerMoreItemsLoadingIndicator.setVisibility(View.VISIBLE); footerCouldNotLoadMoreItemsIndicator.setVisibility(View.INVISIBLE); } else if (nailFetchState == STATUS_ERROR) { footerMoreItemsLoadingIndicator.setVisibility(View.INVISIBLE); footerCouldNotLoadMoreItemsIndicator.setVisibility(View.VISIBLE); } } } @Override public void onServiceConnected(ComponentName name, IBinder service) { // If we didn't start downloading/loading cached bitmaps because the // service was not yet loaded, then notify the adapter that now we can // do so. nailAdapter.notifyDataSetChanged(); } @Override public void onServiceDisconnected(ComponentName name) { // No-op } @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. if (LoggerConfig.canLog(Log.VERBOSE)) { Log.v(TAG, "onCreateLoader(" + id + ", " + args + "): Creating a new cursor loader..."); } return new CursorLoader(getActivity(), ManterestingContract.Nails.CONTENT_URI, null, null, null, ManterestingContract.Nails.NAIL_ID + " DESC"); } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) nailAdapter.swapCursor(data); if (LoggerConfig.canLog(Log.VERBOSE)) { Log.v(TAG, "onLoadFinished(" + loader + ", " + data + "): Swapped nailAdapter cursor."); } } @Override public void onLoaderReset(Loader<Cursor> loader) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. nailAdapter.swapCursor(null); if (LoggerConfig.canLog(Log.VERBOSE)) { Log.v(TAG, "onLoaderReset(" + loader + "): Swapped nailAdapter with a null cursor."); } } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.manteresting_actionbar_context_menu, menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.up: // Illusion of quickly scrolling to the top. Regular scrolling // doesn't seem to work; perhaps because of the // notifyDataSetChanged() from the adapter calls. listView.smoothScrollBy(-5000, 500); listView.postDelayed(new Runnable() { public void run() { listView.smoothScrollBy(0, 0); listView.setSelection(0); } }, 250); return true; default: return false; } } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); getSherlockActivity().getMenuInflater().inflate(R.menu.nail_context_menu, menu); } @Override public boolean onContextItemSelected(android.view.MenuItem item) { AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); if (handleMenuItemSelected(info.position, item.getItemId())) { return true; } else { return super.onContextItemSelected(item); } } private boolean handleMenuItemSelected(int listItemPosition, int itemId) { listItemPosition -= listView.getHeaderViewsCount(); if (listItemPosition >= 0 && listItemPosition < nailAdapter.getCount()) { switch (itemId) { case R.id.share: try { final Cursor cursor = (Cursor) nailAdapter.getItem(listItemPosition); if (cursor != null && !cursor.isClosed()) { final int nailId = cursor.getInt(cursor.getColumnIndex(Nails.NAIL_ID)); final JSONObject nailJson = new JSONObject( cursor.getString(cursor.getColumnIndex(Nails.NAIL_JSON))); final Uri uri = MANTERESTING_SERVER.buildUpon().appendPath("nail") .appendPath(String.valueOf(nailId)).build(); String description = nailJson.getString("description"); if (description.length() > 100) { description = description.substring(0, 97) + ''; } final String user = nailJson.getJSONObject("user").getString("username"); final String category = nailJson.getJSONObject("workbench").getJSONObject("category") .getString("title"); final Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("text/plain"); shareIntent.putExtra(Intent.EXTRA_TEXT, description + ' ' + uri.toString()); shareIntent.putExtra(Intent.EXTRA_SUBJECT, String.format(getResources().getString(R.string.shareSubject), user, category)); try { startActivity(Intent.createChooser(shareIntent, getText(R.string.share))); } catch (ActivityNotFoundException e) { new AlertDialog.Builder(getActivity()).setMessage(R.string.noShareApp).show(); } } } catch (Exception e) { if (LoggerConfig.canLog(Log.WARN)) { Log.w(TAG, "Could not share nail at position " + listItemPosition + " with id " + itemId); } } return true; default: return false; } } else { return false; } } @Override public void onLowMemory() { super.onLowMemory(); if (nailAdapter != null) { nailAdapter.clearCache(); } } @Override public void onDestroyView() { if (LoggerConfig.canLog(Log.VERBOSE)) { Log.d(TAG, "onDestroyView()"); } super.onDestroyView(); // Make sure adapter's cursor has been released, otherwise we can get // exceptions as the old adapter still receives events. nailAdapter.swapCursor(null); serviceConnector.unbindService(); } }