Showing posts with label Common. Show all posts
Showing posts with label Common. Show all posts

Wednesday, June 10, 2015

MyNotes: Color Filter Toolbar Pt. 2

Code Changes

The biggest change that you will notice is the removal of the toolbar fragment.  I decided that with the changes, the Fragment was overkill and fit really well in the MainActivity. 

The Color Filter Toolbar still gets to stay a RecyclerView.  Since I now have two RecyclerViews in the MainActivity, I broke up their initializations into separate methods:


@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);

// Set a toolbar to replace the action bar.
Toolbar toolbar = (Toolbar) findViewById(R.id.activity_main_toolbar);
setSupportActionBar(toolbar);
initializeNoteCards();
initializeColorFilterToolbar();

getLoaderManager().initLoader(MAIN_LOADER_ID, null, this);
}


/**
* Initialize the color filter toolbar
*/
private void initializeColorFilterToolbar() {

int dividerHorizontal =
(int) getResources().getDimension(R.dimen.color_filter_item_divider_horizontal);
int dividerVertical =
(int) getResources().getDimension(R.dimen.color_filter_item_divider_vertical);

mFilterRecyclerView
.addItemDecoration(new DividerItemDecoration(dividerVertical, dividerHorizontal));

boolean vertical = getResources().getBoolean(R.bool.color_filter_toolbar_vertical);
int orientation = vertical ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL;

mFilterRecyclerView.setLayoutManager(new ColorFilterLayoutManager(this, orientation));
mFilterRecyclerView.setAdapter(new FilterColorAdapter(this));
}


/**
* Initialize the note cards UI.
*/
private void initializeNoteCards() {

mCardAdapter = new CardAdapter();

int columnCount = getResources().getInteger(R.integer.card_column_count);
float cardDividerDimen = getResources().getDimension(R.dimen.note_divider_size);

mCardsRecyclerView.addItemDecoration(new DividerItemDecoration((int) cardDividerDimen));
mCardsRecyclerView.setLayoutManager(new GridLayoutManager(this, columnCount));
mCardsRecyclerView.setAdapter(mCardAdapter);
}

Custom Layout Manager


I want the Color Filter Toolbar to fit across the longest side of the device without scrolling.  I also want all of the filter items to be the same size:


$RE77AWB


There isn’t a great way to do this without creating a custom Layout Manager for the recycler view.  Since I still want a single column I extended the LinearLayoutManager and modified the measure routines.



/**
* The height/width for each child.
*/
private int mChildDimension;


/**
* Constructor.
*
* @param context
* The context to use for accessing application resources.
*/
public ColorFilterLayoutManager(final Context context) {
super(context);
}


/**
* Constructor.
*
* @param context
* The context to use for accessing application resources.
* @param orientation
* The orientation of the Views. The only valid values are {@link
* LinearLayoutManager#VERTICAL} or {@link LinearLayoutManager#HORIZONTAL}.
*/
public ColorFilterLayoutManager(final Context context, int orientation) {
super(context, orientation, false);
}


@Override
public void onMeasure(final RecyclerView.Recycler recycler, final RecyclerView.State state,
final int widthSpec, final int heightSpec) {
super.onMeasure(recycler, state, widthSpec, heightSpec);

int itemCount = getItemCount();
if ((itemCount == 0) && (getChildCount() == 0)) {
return;
}

//Determine the expected child dimension for the correct axis.
switch (getOrientation()) {
case VERTICAL:
mChildDimension = (getHeight() - getPaddingBottom() + getPaddingTop()) / itemCount;
break;
case HORIZONTAL:
mChildDimension = (getWidth() - getPaddingLeft() - getPaddingRight()) / itemCount;
break;
}
}


@Override
public void measureChildWithMargins(final View child, final int widthUsed,
final int heightUsed) {
super.measureChildWithMargins(child, widthUsed, heightUsed);

//Determine the child height/width.
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
int width = lp.width;
int height = lp.height;
switch (getOrientation()) {
case VERTICAL:
height = mChildDimension - getBottomDecorationHeight(child) -
getTopDecorationHeight(child);
break;
case HORIZONTAL:
width = mChildDimension - getLeftDecorationWidth(child) -
getRightDecorationWidth(child);
break;
}

//Determine the height and width specs and re-measure the child.
final int widthSpec = getChildMeasureSpec(getWidth(),
getPaddingLeft() + getPaddingRight() +
lp.leftMargin + lp.rightMargin + widthUsed,
width,
canScrollHorizontally());
final int heightSpec =
getChildMeasureSpec(getHeight(),
getPaddingTop() + getPaddingBottom() +
lp.topMargin + lp.bottomMargin + heightUsed,
height,
canScrollVertically());
child.measure(widthSpec, heightSpec);
}
}

The key methods here are the onMeasure and measureChildWithMargins methods.  The onMeasure method calculates a size in one dimension that each child will use.  That size is along the X-axis when using the horizontal orientation and along the Y-axis for vertical orientations. 


The measureChildWithMargins method then uses forces each child use to match that size.  It factors in each child’s padding, margins and decorations to ensure it doesn’t exceed the expected dimension for that child.


Adapter Post Issues


I am the first to admit that I am still learning the nuances of the RecyclerView.  I happened to learn a fun one while testing the latest round of changes.  This one involves an IllegalStateException that occuring if you call any of the Adapters notifyBlah methods while the RecyclerView is laying out or scrolling. 


This is really annoying because things scroll and lay out a lot.   This can be fixed by checking the RecyclerView state before calling any of the notify methods or you can post the notify methods on the UI thread handler and that seems to fix the issue. 


I opted for method two because I am lazy and I don’t like cluttered if branches.  To make the code reusable I extended the RecyclerView.Adapter and created a new version of each notify method.  The new method posts the actual notify call on a supplied handler.  This should eliminate the exception issue.  Emphasis on the should in the last sentence.  The new class is RecyclerViewAdapter in the common library.


RecyclerViewAdapter


public abstract class RecyclerViewAdapter<VH extends RecyclerView.ViewHolder>
extends RecyclerView.Adapter<VH> {


/**
* Asynchronously post {@link #notifyDataSetChanged()} if the handler exists.
*
* @param handler
* the handler to use for posting the requested notify method.
*/
public void postNotifyDataSetChanged(Handler handler) {
if (handler != null) {
handler.post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
}
}


/**
* Asynchronously post {@link #notifyItemChanged(int)} if the handler exists.
*
* @param handler
* the handler to use for posting the requested notify method.
*/
public final void postNotifyItemChanged(Handler handler, final int position) {
if (handler != null) {
handler.post(new Runnable() {
@Override
public void run() {
notifyItemChanged(position);
}
});
}
}


/**
* Asynchronously post {@link #notifyItemRangeChanged(int, int)}.
*
* @param handler
* the handler to use for posting the requested notify method.
*/
public final void postNotifyItemRangeChanged(Handler handler, final int positionStart,
final int itemCount) {

if (handler != null) {
handler.post(new Runnable() {
@Override
public void run() {
notifyItemRangeChanged(positionStart, itemCount);
}
});
}
}


/**
* Asynchronously post {@link #notifyItemInserted(int)}.
*
* @param handler
* the handler to use for posting the requested notify method.
*/
public final void postNotifyItemInserted(Handler handler, final int position) {
if (handler != null) {
handler.post(new Runnable() {
@Override
public void run() {
notifyItemInserted(position);
}
});
}
}


/**
* Asynchronously post {@link #notifyItemMoved(int, int)}.
*
* @param handler
* the handler to use for posting the requested notify method.
*/
public final void postNotifyItemMoved(Handler handler, final int fromPosition,
final int toPosition) {
if (handler != null) {
handler.post(new Runnable() {
@Override
public void run() {
notifyItemMoved(fromPosition, toPosition);
}
});
}
}


/**
* Asynchronously post {@link #notifyItemRangeInserted(int, int)}.
*
* @param handler
* the handler to use for posting the requested notify method.
*/
public final void postNotifyItemRangeInserted(Handler handler, final int positionStart,
final int itemCount) {

if (handler != null) {
handler.post(new Runnable() {
@Override
public void run() {
notifyItemRangeInserted(positionStart, itemCount);
}
});
}
}


/**
* Asynchronously post {@link #notifyItemRemoved(int)}
*
* @param handler
* the handler to use for posting the requested notify method.
*/
public final void postNotifyItemRemoved(Handler handler, final int position) {

if (handler != null) {
handler.post(new Runnable() {
@Override
public void run() {
notifyItemRemoved(position);
}
});
}
}


/**
* Asynchronously post {@link #notifyItemRangeRemoved(int, int)}.
*
* @param handler
* the handler to use for posting the requested notify method.
*/
public final void postNotifyItemRangeRemoved(Handler handler, final int positionStart,
final int itemCount) {
if (handler != null) {
handler.post(new Runnable() {
@Override
public void run() {
notifyItemRangeRemoved(positionStart, itemCount);
}
});
}
}
}

Commit


The commit for these changes can be found at https://github.com/fsk-software/mynotes/commit/e24614e2557ab6791e2f00617895c0fd4e726526.

Wednesday, February 11, 2015

MyNotes: Creating the Data Access Layer in the Common Library.

To start work on the data access layer I need to setup some common classes to make it easier for this app and future apps.

Manage the UI Thread

Since my access layer will be available to any area of the app, I have to worry about my threads more than I would if I was using a ContentProvider.

Prior to Lollipop, Android apps are single threaded by default.   This is often called the UI thread. However, Lollipop added a RenderThread that handles animations to make the smoother.  By default, your app will still perform all work (except animations) on the UI thread unless told to so otherwise. 

In either case, accessing the database on the UI thread is very bad.  You should never do any potentially long-lived operation on the UI thread because it will degrade your UI performance and might lead to an Android Not Responding error.

In order to prevent this, I created a class, ThreadCheck, that validates which thread is running during a method.  I will add a check to each method that access the database to verify that it is not running on the UI thread.  If it is on the UI thread, it will throw a special exception, ThreadException. This will drive out any threading issues quickly and early on.  

To check if a method is called on the UI thread is very easy.  You just have to call Looper.getMainLooper().getThread().  The MainLooper is always associated with the main thread, which happens to always be the UI thread.

Common Ways to Update Items in the Database

Now I need a common approach to updating items in the database.  This is done with an interface, DatabaseStorable, and a Utility class, DatabaseUtilities.  The DatabaseStorable contains methods to store or remove a single item from the database.  Any item that is backed by the database can implement it to update its individual content.

That interface is then used by the DatabaseUtilities to do saves/deletes on a single item or to do them in bulk in a single transaction.

Commits

I did some of the development for these changes late at night after a busy week so it is broken into two commits:

Wednesday, January 28, 2015

MyNotes: Updating the Database

I am going to keep my database schema the same.


I don't really like the names of the database, table, or columns.  However, I don't want to change the schema since the app has already been released and the schema will still work for the expected functionality.  It sucks, but that is one of the trade-offs that have to be made sometimes.

The same cannot be said for the code that defines and manages the database.  That is going to get a major overhaul.

Step 1: Updating the Common Library

My first order of business is create some classes in the CommonLibrary to help with future database needs.  The classes are :

  1. CommonTerms - An interface that defines common terms and phrases used to define and create database schemas. Basically, just a constants class.
  2. DatabaseModel - An interface that defines the common methods for defining and setting up a database.  This will let me use the DatabaseHelper for my database management.
  3. DatabaseHelper - A helper class to manage the databases for an app.
These classes are located under the com.fsk.common.database folder.  Take a peek at them when you have a chance to understand what they are.

Step 2: Adding the CommonLibrary Unit Tests

The first step here is to update the manifest to let me build an apk.  The library manifest suffers the same issue the MyNotes manifest has with respect to the duplicate file problem.  That was discussed briefly in the Setting Up the Unit Tests entry.

Basically, I just added the following entry to the Android block in the CommonLibary gradle file:

    //Required to prevent duplicate files during the build due to the imported libraries.
    packagingOptions {
        exclude 'LICENSE.txt'
        exclude 'META-INF/DEPENDENCIES.txt'
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/NOTICE.txt'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/DEPENDENCIES'
        exclude 'META-INF/notice.txt'
        exclude 'META-INF/license.txt'
        exclude 'META-INF/dependencies.txt'
        exclude 'META-INF/LGPL2.1'
    }

I also added a block to the same gradle file to support java 7:

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

Next, I created the first unit test for the DatabaseHelper file.  I don't need unit tests for the other two files so we can move onto the MyNotes module changes.

Step 3: Updating the MyNotes Module

These changes all resolve around removing or deprecating the old database code and replacing it with the new database setup.

In the original code, the database setup/management was messy and hard to follow because it was split between three main files: DatabaseKeys, NotesTableManager, and DatabaseManager.  Unfortunately, some of the Database management leaked into other classes, which I am going to deal with in the next entry.

For the new design, I moved the entire database schema into the MyNotesDatabaseModel class (com.fsk.mynotes.data.database).  This class implements the CommonLibrary's Database Interface so that I can just use the DatabaseHelper (or an extension of it) to manage my database setup and access.

The original code also has some issues with the timing and survival of the database.  Unfortunately, I created the database in the Activities which meant that sometimes the database went down and there was a lag between the activity starting and the database being ready.

This time around I want to centralize my database initialization into one area that will guarantee it stays alive for the entire application life cycle.  I also want to be sure it is alive before the first activity creates. Luckily, I can do this by creating a custom Application class : MyNotesApplication.

I have to modify the manifest to tell it to use the MyNotesApplication class instead of the system default one. this is done by adding the following line to the manifest application element :

<application
  android:name=".MyNotesApplication"
  android:label="@string/app_name"
  android:icon="@drawable/my_icon">
...
</application>

In MyNotesApplication, I extended the OnCreate method to create the database and initialize the DatabaseHelper with it.

My last step is to do a little cleanup and preparation for removing the NoteTableManager.  The NoteTableManager is going to be removed soon, but it is too big of task for this entry.  For now, I am just going to deprecate and modify it internally to use the DatabaseHelper.

Commits


That is it for this round of mods.  The commit can be found at https://github.com/fsk-software/mynotes/commit/9e368c82f2b5fc7291e2a48127e35ea1da26c7b5


*Technically, the code will run at this point, but it looks bad and is only semi-functional.  The next few sets of changes will probably render it inoperable for a while.

Wednesday, January 21, 2015

MyNotes: Creating the common library.

My first code change is to update the database.  The schema for the DB is okay, but the code could use a little work.  Since databases use a lot of common terms, this is a good opportunity to create some common classes that could be shared with other apps.  For now, I am just going to make this a separate module within my project.  Once I am happy with the module I may move it into its own repository in GitHub.

Step 1 : Create the Module

Creating a new module is pretty easy.  Just do File->New Module. In the dialog choose the library option:

On the next screen, just name your module and give it a package name:



In the next dialog select "No Activity" and press finish.  Once done it will construct the module, complete with the unit test directories for you.


If you haven't used a Library Module before, you will want to review the rules for library modules at the Android Development Blog.  In general, they are incredibly convenient and useful, but be sure to follow the rules for them.
I want my library project to use the same unit test framework as my main project so I am updating its gradle file to include Mockito and Hamcrest as well (See my previous blog entry for how to do that).

The library project comes with some extra stuff that I don't want so I am going to remove the following resources:

  • ic_launcher.png from the various drawable folders 
  • strings.xml from the values folder.
I also modified the CommonLibrary manifest.xml to remove the label and icon attributes from the application element.

The commit is at https://github.com/fsk-software/mynotes/commit/4002903759d580b380b588cf02b1fc146675c211

Step 3: Add the Library Module Dependency


The last step is to add a dependency between the MyNotes Module and the CommonLibrary module.  The steps are:
  1. File->Project Structure...
  2. Select the module that will receive the dependency (MyNotes)
  3. Open the Dependency Tab
  4. Click the green "+" sign
  5. Select "Module Dependency" and select the module (CommonLibrary)
It will look like this afterward:


This will automatically update the MyNotes modules build.gradle filewith the module dependency.

The commit is at https://github.com/fsk-software/mynotes/commit/a92ab9e9bb77d4c340f71e1c5e17a327f2c409eb