Wednesday, June 17, 2015

MyNotes: We all Make Mistakes

 

In one of my last posts I moved all of the logic into the MainActivity.  I changed my mind about that.  I liked the having the fragments more so I reintroduced two fragments: NoteCardsFragment and the ColorFilterFragment.

I then moved the most of the logic out of the MainActivity and into those fragments.

NoteCardsFragment

The same says it all.  This is a fragment that displays all the notes discovered by the loader.  This fragment really just consists of a Loader (moved from the MainActivity), a RecyclerView, and the supporting code to create the RecyclerView. 


/**
* The identifier of the main loader. This loads the color filtered note list.
*/
static final int MAIN_LOADER_ID = 0;


/**
* UI element to display the note data.
*/
@InjectView(R.id.fragment_note_cards_recycler_view)
RecyclerView mCardsRecyclerView;


/**
* The adapter to manage the display of cards in {@link #mCardsRecyclerView}.
*/
CardAdapter mCardAdapter;


/**
* The listener to a note being clicked.
*/
final CardAdapter.OnItemClickListener mNoteClickListener =
new CardAdapter.OnItemClickListener() {

@Override
public void onItemClick(final View view, final Note note) {
editNote(view, note);
}
};


@Nullable
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_note_cards, container, false);
ButterKnife.inject(this, rootView);

Context context = inflater.getContext();
mCardAdapter = new CardAdapter();
mCardAdapter.setOnItemClickListener(mNoteClickListener);

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

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

return rootView;
}


@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(MAIN_LOADER_ID, null, this);
}


@Override
public Loader<List<Note>> onCreateLoader(final int id, final Bundle args) {
return new FilteredNoteLoader(getActivity());
}


@Override
public void onLoadFinished(final Loader<List<Note>> loader, final List<Note> data) {
mCardAdapter.setNotes(data);
mCardAdapter.postNotifyDataSetChanged(mCardsRecyclerView.getHandler());
}


@Override
public void onLoaderReset(final Loader<List<Note>> loader) {
//Space for rent
}


At this point, the biggest lesson to take away is that I don’t create the loader until onActivityCreated.  This is very important because it is the earliest point in the lifecycle that I can guarantee that both the Fragments main view (getView()) and the Activity are ready.




@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getLoaderManager().initLoader(MAIN_LOADER_ID, null, this);
}


One other minor thing to note is that I normally do a public static method that creates my Fragments.  These are normally named something like newInstance.



/**
* Create a new instance of the {@link NoteCardsFragment}
*
* @return a new instance of the {@link NoteCardsFragment}
*/
public static NoteCardsFragment newInstance() {
return new NoteCardsFragment();
}



Normally, Fragment creation goes something like this:


Fragment fragment = new MyFragment();
Bundle arguments = new Bundle();
arguments.putInt(KEY, VALUE);
fragment.setArguments(arguments);

It may not seem like it matters but the newInstance code (along with make all the argument keys private) converts a runtime bugs into a compile time bugs.  To me that is a huge deal, especially on larger projects.


ColorFilterFragment


This fragment is responsible for filtering notes by color.  It is just the RecyclerView and its supporting data.  This was directly ported from the MainActivity.


public class ColorFilterFragment extends Fragment {

/**
* Create a new instance of the {@link ColorFilterFragment}
*
* @return a new instance of the {@link ColorFilterFragment}
*/
public static ColorFilterFragment newInstance() {
return new ColorFilterFragment();
}


/**
* UI element that allows the user to filter notes by color.
*/
@InjectView(R.id.fragment_color_filter_recycler)
RecyclerView mFilterRecyclerView;


/**
* The Cache for the selected colors.
*/
NoteFilterCache mNoteFilterCache;


/**
* The listener to react to changes to the color filter selection.
*/
final FilterColorFilterAdapter.OnColorChangeListener mOnColorSelectChangeListener =
new FilterColorFilterAdapter.OnColorChangeListener() {
@Override
public void onColorSelected(final NoteColor color, final boolean enabled) {
mNoteFilterCache.enableColor(color, enabled);
}
};


@Nullable
@Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
final Bundle savedInstanceState) {

View rootView = inflater.inflate(R.layout.fragment_color_selector, container, false);
ButterKnife.inject(this, rootView);

Context context = inflater.getContext();
mNoteFilterCache = new NoteFilterCache(context);

Resources resources = context.getResources();
mFilterRecyclerView.setAdapter(createAdapter(context));
mFilterRecyclerView.addItemDecoration(createDividerItemDecoration(resources));
mFilterRecyclerView.setLayoutManager(createLayoutManager(context, resources));

return rootView;
}


/**
* Create the adapter.
*
* @param context
* The context to use for creating the adapter.
*
* @return The adapter for managing {@link #mFilterRecyclerView}.
*/
private FilterColorFilterAdapter createAdapter(@NonNull Context context) {
FilterColorFilterAdapter adapter = new FilterColorFilterAdapter(context);
adapter.setOnColorChangeListener(mOnColorSelectChangeListener);
adapter.setSelectedColors(mNoteFilterCache.getEnabledColors());

return adapter;
}


/**
* Create a {@link DividerItemDecoration} for the RecyclerView.
*
* @param resources
* The resources to read for divider and orientation data.
*
* @return a new {@link DividerItemDecoration}.
*/
private DividerItemDecoration createDividerItemDecoration(Resources resources) {
int dividerHorizontal =
(int) resources.getDimension(R.dimen.color_filter_item_divider_horizontal);
int dividerVertical =
(int) resources.getDimension(R.dimen.color_filter_item_divider_vertical);

return new DividerItemDecoration(dividerVertical, dividerHorizontal);
}


/**
* Create a {@link ColorFilterLayoutManager} for the RecyclerView.
*
* @param context
* The context to assign to the layout manager.
* @param resources
* The resources to read for divider and orientation data.
*
* @return a new {@link ColorFilterLayoutManager}.
*/
private RecyclerView.LayoutManager createLayoutManager(Context context, Resources resources) {
boolean vertical = resources.getBoolean(R.bool.color_filter_toolbar_vertical);
int orientation = vertical ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL;
return new ColorFilterLayoutManager(context, orientation);
}
}

MainActivity


Last, but not least, is the MainActivity.  This class was gutted of most of the business logic.  It is now responsible for adding notes and adding the above fragments. 


/**
* The primary activity. It provides a list of notes and the tools to manage them.
*/
public class MainActivity extends AppCompatActivity {

/**
* The fragment tags.
*/
static class FragmentTags {
/**
* The tag for the note cards fragment.
*/
public static final String NOTE_CARDS_FRAGMENT_TAG = "NOTE_CARDS_FRAGMENT_TAG";

/**
* The tag for the color filter fragment.
*/
public static final String COLOR_FILTER_FRAGMENT_TAG = "COLOR_FILTER_FRAGMENT_TAG";
}

/**
* UI element that displays a toolbar
*/
@InjectView(R.id.activity_main_toolbar)
Toolbar mToolbar;


/**
* UI element that allows the user to add a note.
* @param view
*/
@OnClick(R.id.activity_main_add_view)
public void addViewClick(View view) {
createNewNote();
}


@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);

Fragment colorFilterSelectorFragment = ColorFilterFragment.newInstance();
Fragment noteCardsFragment = NoteCardsFragment.newInstance();

FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.activity_main_notes_container,
noteCardsFragment,
FragmentTags.NOTE_CARDS_FRAGMENT_TAG);

transaction.replace(R.id.activity_main_color_filter_container,
colorFilterSelectorFragment,
FragmentTags.COLOR_FILTER_FRAGMENT_TAG);
transaction.commit();
}


/**
* Launch a UI that allows the user to create a new note.
*/
private void createNewNote() {
startActivity(EditNoteActivity.createIntent(this, null));
}
}



Commits


These changes can be found at commits https://github.com/fsk-software/mynotes/commit/a0632503c5fabf74a31cca091d7975419d002328 and https://github.com/fsk-software/mynotes/commit/ec427c6a5684220836f823824d58a61cd2aea47b.

No comments :

Post a Comment