We can use a different projection and the same observer pattern for displaying some KPIs. Actually that is pretty easy, as we will see in this recipe.
We will continue working on the app from the previous recipe and we will add a new view to display the KPIs:
fragment_thoughts_kpi.xml
:<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android= "http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:gravity="center_horizontal" android:padding="16dp" android:layout_height="match_parent"> <TextView android:id="@+id/thoughts_kpi_count" android:textSize="32sp" android:layout_margin="16dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/thoughts_kpi_avg_happiness" android:text= "@string/average_happiness" android:textSize="32sp" android:layout_margin="16dp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <RatingBar android:id="@+id/thoughts_rating_bar_happy" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:clickable="false" android:numStars="5" android:rating="0" /> </LinearLayout>
ThoughtsKpiFragment
. It descends from the Fragment
class. We will be using the LoaderManager
here as well so it will basically look like this:public class ThoughtsKpiFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> { @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) {return null; } @Override public void onLoadFinished(Loader<Cursor> loader, Cursordata) { } @Override public void onLoaderReset(Loader<Cursor> loader) { } }
public static int LOADER_COUNT_THOUGHTS = 1; public static int LOADER_AVG_RATING = 2;
onCreate
method:@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate( R.layout.fragment_thoughts_kpi, container, false); getKpis(); return view; }
getKpis
method (where we initialize the loader twice for different purposes):private void getKpis(){ getLoaderManager().initLoader(LOADER_COUNT_THOUGHTS, null, this); getLoaderManager().initLoader(LOADER_AVG_RATING, null, this); }
onCreateLoader
method. This time the projection depends on the id of the loader. The projection is just like you would expect it to be if it was plain SQL. We are counting the number of rows and we are calculating the average happiness:@Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { if (id == LOADER_COUNT_THOUGHTS) { String[] projection = new String[] {"COUNT(*) AS kpi"}; android.content.CursorLoader cursorLoader = new android.content.CursorLoader(getActivity(), ThoughtsProvider.CONTENT_URI, projection, null, null, null); return cursorLoader; } else { String[] projection = new String[] {"AVG(happiness) AS kpi"}; android.content.CursorLoader cursorLoader = new android.content.CursorLoader(getActivity(), ThoughtsProvider.CONTENT_URI, projection, null, null, null); return cursorLoader;} }
onLoadFinished
method and will call methods to display the data, if there is any:@Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { if (data == null || !data.moveToNext()) { return; } if (loader.getId() == LOADER_COUNT_THOUGHTS) { setCountedThoughts(data.getInt(0)); } else{ setAvgHappiness(data.getFloat(0)); } }
setCountedThoughts
and setAvgHappiness
methods. If the fragment is still attached to the activity, we will update the text view or the rating bar:private void setCountedThoughts(final int counted){ if (getActivity()==null){ return; } getActivity().runOnUiThread(new Runnable() { @Override public void run() { TextView countText = (TextView)getView().findViewById( R.id.thoughts_kpi_count); countText.setText(String.valueOf(counted)); } }); } private void setAvgHappiness(final float avg){ if (getActivity()==null){ return; } getActivity().runOnUiThread(new Runnable() { @Override public void run() { RatingBar ratingBar = (RatingBar)getView().findViewById( R.id.thoughts_rating_bar_happy); ratingBar.setRating(avg);} }); }
MainActivity
file, add a private member for the KPI fragment:private ThoughtsKpiFragment mThoughtsKpiFragment;
getKpiFragment
:private ThoughtsKpiFragment getKpiFragment(){ if (mThoughtsKpiFragment==null){ mThoughtsKpiFragment = new ThoughtsKpiFragment(); } return mThoughtsKpiFragment; }
onNavigationDraweItemSelected
method and add this to the if
statement:… else if (position==0){ fragmentManager.beginTransaction() .replace(R.id.container, getKpiFragment()) .commit(); }
Run your app. Now we have some neat statistics in our thoughts app:
In this and in the previous recipe, we have seen how easy working with data becomes once you have grokked the concept of content providers.
So far we did all this within the same app; however, since we are already prepared to export the content provider, let us find out how to read our thoughts in a different app. Let's do that now.