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)
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
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
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();
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
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();
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
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);
}
private static final String TAG = "ShoppingListReviewFragment to private static final String TAG = ShoppingListReviewFragment.class.getSimpleName(); Separated the view holders, adapters, and fragments
// 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
);