Skip to content

Instantly share code, notes, and snippets.

@z0lope0z
Last active August 29, 2015 14:23
Show Gist options
  • Save z0lope0z/b15f5564a6b9a40aa5f2 to your computer and use it in GitHub Desktop.
Save z0lope0z/b15f5564a6b9a40aa5f2 to your computer and use it in GitHub Desktop.
PR notes

This PR has many changes so I'd like to break it down, especially since I've bundled multiple tickets (bad practice, just this once I promise! huhu)

Breakdown

Use of DTOs(Data Transfer Objects)

DTOs are currently used as a contracts for views as well as a bundling object when methods need to return multiple objects of different types (java doesn’t support returning of tuples)

syncData() now returns a Data Transfer Object that bundles {orderItem, suggestionMap, and replacementMap} so that inheriting classes can just override, call super method and retrieve the updated data

Introduction of command pattern + builder pattern for each user interaction

The command pattern encapsulates the business logic needed to be executed when performing certain actions, i.e.

action: 
move order item back to the ShoppingList(products yet to be found by shopper)
procedure:
reset order item quantity in cart
reset order item status
reset order item fulfilment status
reset shopper notes
reset replacement item

Once we’ve built the command (refer to end), we just call execute() and listen to any validation errors (since the command pattern’s implementation here supports validation)

Why not just bundle it together into one function? Bundling it together into one function is okay. I just: Don’t want to end up with a large class that implements so many methods.

Additionally, the command pattern provides a higher abstraction than bundling it together in one class. In fact, one can bundle it together and call that function using the command. This adds more options for refactoring.

Advantage: higher level of abstraction classes of type command have one and only one responsibility - executing that command. added validation command execution listeners commands can be unit tested if we figure out a way to mock/listen to ShoppingUtils class

Disadvantage: a lot more boilerplate code additional layer also means additional layer of where code can fail (but at least it’s controlled on that layer) will need to instantiate additional objects

Todo: I haven’t implemented this to all the methods, so will need to do an audit

Builder Pattern

Unlike in python or ruby, java doesn’t support keyword arguments so we have to go around this using the Builder pattern. Sometimes, commands can require a lot of parameters so we use methods to create that object.

i.e. suggest replacement command

SuggestReplacementCommand command = new SuggestReplacementCommandBuilder(itemId, fulfillmentId)
        .setExecutionListener(new CommandImpl.ExecutionListener<SuggestReplacementCommand>() {
            @Override
            public void onExecuting() {
                fragment.dismiss();
                dismiss();
            }

            @Override
            public void onError(String reason) {
                UIUtils.showToast(getActivity(), reason);
            }
        })
        .setStatus(ShoppingUtils.checkStatusFromTag(tag))
        .setTag(tag)
        .setOrderItem(orderItem)
        .setQuantity(quantity)
        .createSuggestReplacementCommand();
command.execute();

Resource ID naming convention

introduced naming convention, lvw_, txt_, lbl_ just type in lvw ctrl+space to get that list view or txt ctrl+space to get that textview

Use of Google Guava

ComparisonChain

Google’s utility such as ComparisonChain, which lets us do a rank ordering fulfilment items using their fulfilment status. In this case we want the job to be at the top of the list, and the completed jobs to be at the bottom of the list. This lets us do other complex stuff as well.

see how sexy guava can be:

ComparisonChain
        .start()
        .compareTrueFirst(
                left.getShopperStatusEnum() == FulfillmentTypeStatus.STARTED,
                right.getShopperStatusEnum() == FulfillmentTypeStatus.STARTED
        )
        .compareTrueFirst(
                left.getShopperStatusEnum() == FulfillmentTypeStatus.PENDING_ACCEPTANCE,
                right.getShopperStatusEnum() == FulfillmentTypeStatus.PENDING_ACCEPTANCE
        )
        .compareTrueFirst(
                left.getShopperStatusEnum() == FulfillmentTypeStatus.PENDING_START,
                right.getShopperStatusEnum() == FulfillmentTypeStatus.PENDING_START
        )
        .compareTrueFirst(
                left.getShopperStatusEnum() == FulfillmentTypeStatus.UNASSIGNED,
                right.getShopperStatusEnum() == FulfillmentTypeStatus.UNASSIGNED
        )
        .compareTrueFirst(
                left.getShopperStatusEnum() == FulfillmentTypeStatus.REJECTED,
                right.getShopperStatusEnum() == FulfillmentTypeStatus.REJECTED
        )
        .compareTrueFirst(
                left.getShopperStatusEnum() == FulfillmentTypeStatus.COMPLETED,
                right.getShopperStatusEnum() == FulfillmentTypeStatus.COMPLETED
        )
        .result();

Preconditions

This lets us fail early if certain assumed conditions are not met. For example, we always assume that order id and fulfilment id are not null or empty, so we make sure by failing hard if commands are supplied otherwise. This lets us detect bugs ASAP.

checkNotNull(fulfillmentId, "FulfilmentID must not be null");
checkNotNull(orderItemId, "OrderItemID must not be null");
// continue code knowing full well that fulfillmentId and orderItemId will NEVER be null at this point

Use of greendroid’s EventBus

I use this to switch from one page to another. I can use a broadcast receiver but I think there’s less code needed with eventbus (plus I can explicitly enforce that the listener be called using the main thread or ui thread)

\\ calling EventBus.getDefault().post(new Event.ReviewItemButtonClickedEvent());
\\ will call onEventMainThread
public void onEventMainThread(Event.ReviewItemButtonClickedEvent event) {
    switchToPage(2, true);
}

Specifying the TAG directly through class reflection:

private static final String TAG = "ShoppingListReviewFragment to private static final String TAG = ShoppingListReviewFragment.class.getSimpleName(); Separated the view holders, adapters, and fragments

Used an interface to react to user interaction, encapsulating the button ids to the adapter.

// adapter listener
public void onItemClick(View v, OrderItem orderItem, Bundle extra) {
    switch (v.getId()) {
        case R.id.btn_dont_replace:
            onReviewProductListener.onDontReplace(orderItem);
            break;
        case R.id.btn_replace:
            onReviewProductListener.onReplace(orderItem);
            break;
        case R.id.btn_move_back_to_main:
            onReviewProductListener.onMoveBackToItemsList(orderItem);
            break;
        case R.id.btn_buy:
            onReviewProductListener.onBuy(orderItem);
            break;
        case R.id.btn_dont_buy:
            onReviewProductListener.onDontBuy(orderItem);
            break;
        case R.id.btn_confirm_quantity:
            onReviewProductListener.onConfirmQuantity(orderItem);
            break;
    }
}
// interface to be implemented by fragment and passed to adapter
public interface OnReviewProductListener {
    void onDontReplace(OrderItem orderItem);

    void onReplace(OrderItem orderItem);

    void onMoveBackToItemsList(OrderItem orderItem);

    void onBuy(OrderItem orderItem);

    void onDontBuy(OrderItem orderItem);

    void onConfirmQuantity(OrderItem orderItem);
}
// adapter constructor
OnReviewProductListener onReviewProductListener;
HashMap<String, OrderItem> replacementItems;

public ReviewProductsAdapter(Context context,
                             List<OrderItem> productList,
                             HashMap<String, OrderItem> replacementItems,
                             OnReviewProductListener onReviewProductListener) {
    super(context, R.layout.holder_shoppingcart_review_item, productList);
    this.onReviewProductListener = onReviewProductListener;
    this.replacementItems = replacementItems;
}
// calling fragment
ReviewProductsAdapter.OnReviewProductListener onReviewProductListener =
        new ReviewProductsAdapter.OnReviewProductListener() {
            @Override
            public void onDontReplace(OrderItem orderItem) {
                ShoppingUtils.sendOrderItemFulfilmentStatus(fulfillmentId, orderItem.getId(), ShoppingUtils.getRejectedStatusFromTag(orderItem.getTag()));
            }

            @Override
            public void onReplace(OrderItem orderItem) {
                ShoppingUtils.sendOrderItemFulfilmentStatus(fulfillmentId, orderItem.getId(), ShoppingUtils.getAcceptedStatusFromTag(orderItem.getTag()));
            }

            @Override
            public void onMoveBackToItemsList(OrderItem orderItem) {
                MoveBackToItemListCommand command = new MoveBackToItemListCommandBuilder(fulfillmentId, orderItem).createMoveBackToItemListCommand();
                command.execute();
            }

            @Override
            public void onBuy(OrderItem orderItem) {
                BuyLowStockCommand command = new BuyLowStockCommandBuilder(fulfillmentId, orderItem.getId())
                        .setTag(orderItem.getTag())
                        .setExecutionListener(new CommandImpl.ExecutionListener() {
                            @Override
                            public void onExecuting() {
                                // do nothing, autoload
                            }

                            @Override
                            public void onError(String reason) {
                                UIUtils.showToast(getActivity(), reason);
                            }
                        })
                        .createBuyLowStockCommand();
                command.execute();
            }

            @Override
            public void onDontBuy(OrderItem orderItem) {
                DontBuyLowStockCommand command = new DontBuyLowStockCommandBuilder(fulfillmentId, orderItem.getId())
                        .setTag(orderItem.getTag())
                        .createDontBuyLowStockCommand();
                command.execute();
            }

            @Override
            public void onConfirmQuantity(OrderItem orderItem) {
                ConfirmQuantityCommand command = new ConfirmQuantityCommandBuilder(fulfillmentId, orderItem.getId())
                        .setTag(orderItem.getTag())
                        .createConfirmQuantityCommand();
                command.execute();
            }
        };

adapter = new ReviewProductsAdapter(getActivity(),
        new ArrayList<OrderItem>(),
        new HashMap<String, OrderItem>(),
        onReviewProductListener
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment