31 October 2016

ว่าด้วยเรื่อง Recycler View กับการใช้งานจริงในแบบฉบับเจ้าของบล็อก ตอนที่ 3

Updated on


        ในตอนล่าสุดเจ้าของบล็อกก็ได้แสดงตัวอย่างการเรียกใช้งาน Recycler View ที่ดูเหมือนว่าจะเสร็จแล้ว แต่สุดท้ายแล้วก็ยังไม่เสร็จดีนัก ซึ่งในบทความนี้ก็จะมาแสดงให้เห็นกันว่าการเขียนแบบที่เจ้าของบล็อกใช้นั้นมันแก้ไขได้ง่ายจริงหรือ? มาอ่านกันต่อได้เลย~

บทความที่เกี่ยวข้อง

        • ว่าด้วยเรื่อง Recycler View กับการใช้งานจริงในแบบฉบับเจ้าของบล็อก ตอนที่ 1
        • ว่าด้วยเรื่อง Recycler View กับการใช้งานจริงในแบบฉบับเจ้าของบล็อก ตอนที่ 2
        • ว่าด้วยเรื่อง Recycler View กับการใช้งานจริงในแบบฉบับเจ้าของบล็อก ตอนที่ 3
        • ว่าด้วยเรื่อง Recycler View กับการใช้งานจริงในแบบฉบับเจ้าของบล็อก ตอนที่ 4
        • ว่าด้วยเรื่อง Recycler View กับการใช้งานจริงในแบบฉบับเจ้าของบล็อก ตอนที่ 5
        • ว่าด้วยเรื่อง Recycler View กับการใช้งานจริงในแบบฉบับเจ้าของบล็อก ตอนที่ 6

ย้อนความ

        ในตอนล่าสุดเจ้าของบล็อกได้สร้าง Recycler View ขึ้นมาโดยมีผลลัพธ์แบบนี้


        ผู้ที่หลงเข้ามาอ่านสามารถดาวน์โหลดตัวอย่างได้จาก Lovely Recycler View - Branch part_2_ending [GitHub] เพื่อความต่อเนื่องครับ

        แต่ทีนี้ปัญหาที่เกิดขึ้นคือ มันดันไม่ตรงกับ Requirement ที่กำหนดไว้ในตอนแรก ตรงที่สีของ Section นั้นจะต้องเปลี่ยนไปตามประเภทของสินค้า


        ดังนั้นเจ้าของบล็อกจะแก้ไขยังไงดีล่ะ?

มามะ มาทำให้มันถูกต้องกันเถอะ

        อย่างแรกเลยคือ Layout XML ของ Section นั้นมีการกำหนดสีพื้นหลังที่ Linear Layout ที่ไม่ได้ประกาศ ID ไว้ ดังนั้นจะต้องประกาศ​ ID และ Binding ไว้ใน Section View Holder ให้เรียบร้อยเสียก่อน ขอกำหนดเป็น @+id/layout_section_container ละกัน

view_section.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/layout_section_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="@dimen/margin_padding_small"
    android:layout_marginEnd="@dimen/margin_padding"
    android:layout_marginStart="@dimen/margin_padding"
    android:layout_marginTop="@dimen/margin_padding_small"
    android:background="@color/sky_light_blue"
    android:orientation="horizontal"
    android:paddingBottom="@dimen/margin_padding_small"
    android:paddingEnd="@dimen/margin_padding_large"
    android:paddingStart="@dimen/margin_padding_large"
    android:paddingTop="@dimen/margin_padding_small">

    <TextView
        android:id="@+id/tv_section"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@color/angel_white"
        android:textSize="@dimen/text_size" />

</LinearLayout>

SectionViewHolder.java

import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;


public class SectionViewHolder extends RecyclerView.ViewHolder {
    public LinearLayout layoutSectionContainer;
    public TextView tvSection;

    public SectionViewHolder(View itemView) {
        super(itemView);
        layoutSectionContainer = (LinearLayout) itemView.findViewById(R.id.layout_section_container);
        tvSection = (TextView) itemView.findViewById(R.id.tv_section);
    }
}

        แล้วเจ้าของบล็อกควรจะกำหนดสีพื้นหลังที่ไหนในโค้ดล่ะ?

        เจ้าของบล็อกจะกำหนดสีพื้นหลังจากใน Adapter แบบนี้เลยก็ได้

private void setupSection(SectionViewHolder sectionViewHolder, SectionItem sectionItem) {
    sectionViewHolder.tvSection.setText(sectionItem.getSection());

    Context context = sectionViewHolder.itemView.getContext();
    String section = sectionItem.getSection();
    if (section.equals(context.getString(R.string.food))) {
        sectionViewHolder.layoutSectionContainer.setBackgroundColor(ContextCompat.getColor(context, R.color.sky_light_blue));
    } else if (section.equals(context.getString(R.string.book))) {
        sectionViewHolder.layoutSectionContainer.setBackgroundColor(ContextCompat.getColor(context, R.color.funny_dark_pink));
    } else if (section.equals(context.getString(R.string.music))) {
        sectionViewHolder.layoutSectionContainer.setBackgroundColor(ContextCompat.getColor(context, R.color.natural_green));
    }
}

        แต่แบบนี้โอเคหรือป่าวนะ? เพราะเดิมทีวิธีของเจ้าของบล็อกไม่อยากให้ข้างใน Adapter มี Logic ที่ดูซับซ้อนซักเท่าไรนัก ถึงแม้จะเป็นโค้ดง่ายๆแบบนี้ก็ตาม เพราะสุดท้ายแล้วเมื่อมีโค้ดแบบนี้เพิ่มเข้าไปทีละนิดทีละหน่อย สุดท้ายมันก็จะกลายเป็น Adapter ที่เต็มไปด้วย Logic เยอะแยะไปหมดอยู่ดี

        ดังนั้นเพื่อลดความซับซ้อนข้างใน และทำโค้ดให้ดูง่ายขึ้นกว่านี้ เจ้าของบล็อกจึงเลือกที่จะแก้ไขโดยการเพิ่ม Property เข้าไปในคลาส SectionItem อีกหนึ่งตัว นั้นก็คือค่าสีของ Section

SecionItem.java
public class SectionItem extends BaseOrderDetailItem {
    private String section;
    private int backgroundColor;

    public SectionItem() {
        super(OrderDetailType.TYPE_SECTION);
    }

    public String getSection() {
        return section;
    }

    public void setSection(String section) {
        this.section = section;
    }

    public int getBackgroundColor() {
        return backgroundColor;
    }

    public void setBackgroundColor(int backgroundColor) {
        this.backgroundColor = backgroundColor;
    }
}

        แล้วก็แก้ไขใน Method ที่ชื่อว่า createSection ในคลาส OrderDetailConverter เพื่อให้เพิ่มค่าสีเข้าไปใน SectionItem ด้วย

OrderDetailConverter.java
private static SectionItem createSection(String title, int titleColor) {
    SectionItem sectionItem = new SectionItem();
    sectionItem.setSection(title);
    sectionItem.setBackgroundColor(titleColor);
    return sectionItem;
}

        แล้วก็ในคำสั่ง getFoodOrderDetailList, getBookOrderDetailList และ getMusicOrderDetailList ก็จะต้องปรับคำสั่งตามด้วย เพราะทั้ง 3 Method นี้เรียกใช้คำสั่ง createSection อยู่

OrderDetailConverter.java
private static List<BaseOrderDetailItem> getFoodOrderDetailList(List<OrderDetail.Food> foodList, String foodTitle, String currency, int foodTitleColor) {
    List<BaseOrderDetailItem> foodOrderDetailList = new ArrayList<>();
    if (foodList != null && foodList.size() > 0) {
        foodOrderDetailList.add(createSection(foodTitle, foodTitleColor));
        for (OrderDetail.Food food : foodList) {
            String name = food.getOrderName();
            String detail = "x" + food.getAmount();
            String price = food.getPrice() + currency;
            foodOrderDetailList.add(createOrder(name, detail, price));
        }
    }
    return foodOrderDetailList;
}

private static List<BaseOrderDetailItem> getBookOrderDetailList(List<OrderDetail.Book> bookList, String bookTitle, String currency, int bookTitleColor) {
    List<BaseOrderDetailItem> bookOrderDetailList = new ArrayList<>();
    if (bookList != null && bookList.size() > 0) {
        bookOrderDetailList.add(createSection(bookTitle, bookTitleColor));
        for (OrderDetail.Book book : bookList) {
            String name = book.getBookName();
            String detail = book.getAuthor();
            String price = book.getPrice() + currency;
            bookOrderDetailList.add(createOrder(name, detail, price));
        }
    }
    return bookOrderDetailList;
}

private static List<BaseOrderDetailItem> getMusicOrderDetailList(List<OrderDetail.Music> musicList, String musicTitle, String currency, int musicTitleColor) {
    List<BaseOrderDetailItem> musicOrderDetailList = new ArrayList<>();
    if (musicList != null && musicList.size() > 0) {
        musicOrderDetailList.add(createSection(musicTitle, musicTitleColor));
        for (OrderDetail.Music music : musicList) {
            String name = music.getAlbum();
            String detail = music.getArtist();
            String price = music.getPrice() + currency;
            musicOrderDetailList.add(createOrder(name, detail, price));
        }
    }
    return musicOrderDetailList;
}

        ดังนั้นเจ้าของบล็อกจึงเพิ่มให้ทั้ง 3 Method ที่ว่านี้มีการรับค่า Integer สำหรับค่าสีเข้ามาด้วยอีกทีหนึ่ง

        แล้วก็ไปแก้ไขใน createSectionAndOrder โดยให้รับค่าสีเพิ่มเข้ามาเพื่อกำหนดใน Section แต่ละแบบ

OrderDetailConverter.java
public static List<BaseOrderDetailItem> createSectionAndOrder(OrderDetail orderDetail,
                                                              String foodTitle,
                                                              String bookTitle,
                                                              String musicTitle,
                                                              String currency,
                                                              int foodTitleColor,
                                                              int bookTitleColor,
                                                              int musicTitleColor) {
    List<BaseOrderDetailItem> orderDetailItemList = new ArrayList<>();
    orderDetailItemList.addAll(getFoodOrderDetailList(orderDetail.getFoodList(), foodTitle, currency, foodTitleColor));
    orderDetailItemList.addAll(getBookOrderDetailList(orderDetail.getBookList(), bookTitle, currency, bookTitleColor));
    orderDetailItemList.addAll(getMusicOrderDetailList(orderDetail.getMusicList(), musicTitle, currency, musicTitleColor));
    return orderDetailItemList;
}

        ซึ่งท้ายที่สุดแล้วค่าสีของ Section แต่ละตัวนั้นก็จะถูกกำหนดจากต้นทางหรือที่ Activity นั่นเอง โดยดึงค่าสีจาก Color Resource แล้วส่งเข้าไปใน OrderDetailConverter เพื่อกำหนดค่าให้กับ SectionItem

MainActivity.java
private void setOrderDetail(OrderDetail orderDetail) {
    ...

    String foodTitle = getString(R.string.food);
    String bookTitle = getString(R.string.book);
    String musicTitle = getString(R.string.music);
    String currency = getString(R.string.baht_unit);

    int foodTitleColor = ContextCompat.getColor(this, R.color.sky_light_blue);
    int bookTitleColor = ContextCompat.getColor(this, R.color.funny_dark_pink);
    int musicTitleColor = ContextCompat.getColor(this, R.color.natural_green);

    List<BaseOrderDetailItem> orderDetailItemList = new ArrayList<>();
    
    ...

    orderDetailItemList.addAll(OrderDetailConverter.createSectionAndOrder(orderDetail, foodTitle, bookTitle, musicTitle, currency, foodTitleColor, bookTitleColor, musicTitleColor));
    
    ...
}

        หมายเหตุ - อย่างที่บอกในตอนแรกว่ารูปแบบคำสั่งในจุดนี้สามารถปรับเปลี่ยนได้ตามความเหมาะสม เจ้าของบล็อกประกาศยาวๆแบบนี้เพื่อให้ผู้ที่หลงเข้ามาอ่านสามารถดูได้สะดวกเท่านั้น

        หมายเหตุ 2 - ควรใช้ ContextCompat ในการดึงค่าสีจาก Color Resource เพื่อให้รองรับกับทุกเวอร์ชัน เนื่องจาก Android 5.0 Lollipop มีการเปลี่ยนวิธีดึงค่าสีจากของเดิม ทีม Android จึงสร้างคลาส ContextCompat ขึ้นมาเพื่อช่วยให้ไม่ต้องเขียนโค้ดแยกเวอร์ชัน

        และเมื่อกลับมาดูที่คลาส OrderAdapter ก็จะเหลือโค้ดแค่นี้

OrderAdapter.java
...

private void setupSection(SectionViewHolder sectionViewHolder, SectionItem sectionItem) {
    sectionViewHolder.tvSection.setText(sectionItem.getSection());
    sectionViewHolder.layoutSectionContainer.setBackgroundColor(sectionItem.getBackgroundColor());
}

        เมื่อดูผลลัพธ์ที่เกิดขึ้นก็จะเห็นว่า Section เปลี่ยนสีตามที่กำหนดแล้ว แต่สาเหตุสาเหตุที่กำหนดค่าสีผ่าน OrderDetailConverter นั้นก็เพื่อให้ Logic ที่จะต้องเกิดขึ้นนั้นอยู่ในคลาสตัวนี้แทนที่จะเป็น Adapter โดยจะเห็นว่าคลาส OrderAdapter ต้องทำก็แค่ดึงค่าสีจาก SectionItem มากำหนดได้เลย


        อาจจะดูเหมือนยุ่งยากต้องแก้ไขหลายจุด แต่เมื่อดูโค้ดโดยรวมทั้งหมดแล้วก็จะพบว่าโค้ดแต่ละส่วนถูกแยกออกจากการเป็นระเบียบมากกว่าซึ่งเมื่อมีการแก้ไขเพิ่มเติมก็แค่ดูว่าสิ่งที่จะต้องแก้ไขนั้นอยู่ในส่วนใด แล้วแก้ไขแค่ส่วนนั้นๆแทน

อ่ะ แค่นี้คงเล็กน้อย มาดูอีกหนึ่งตัวอย่างกัน

        ถึงแม้ว่าโค้ดนี้จะดูเรียบร้อยแล้ว พร้อมใช้งานแล้ว แต่ลองนึกภาพว่าถ้าลูกค้ามีบางอย่างที่อยากจะให้เพิ่มเข้าไปล่ะ?

        "น้องครับๆ ถ้า Web Service ไม่ส่งข้อมูลอะไรมาให้ ให้แสดงเป็น...."

        ก็นั่นล่ะฮะ ความรู้สึกที่คุ้นเคย

        เอ...ถ้างั้นก็ต้องจำลองว่า Web Service ไม่ส่งข้อมูลมาให้สินะ.. ข้อมูลที่ส่งกลับมาจะเป็นแบบไหนได้มั่งหว่า?

แบบที่หนึ่ง
{
  "food_list": [],
  "book_list": [],
  "music_list": []
}

แบบที่สอง
{
  "food_list": null,
  "book_list": null,
  "music_list": null
}

แบบที่สาม
{ }

แบบที่สี่
//ไม่มีอะไรอยู่เลย

แบบที่ห้า
null

        สาบานได้ว่าเคยเจอแบบที่ห้าจริงๆนะ...


ปรับให้คลาส FakeNetwork สามารถส่งข้อมูลได้หลายแบบเสียก่อน

        ก็ไม่มีอะไรมากนัก ก็แค่ให้คลาสดังกล่าวสุ่มผลลัพธ์ที่จะส่งกลับมานั่นเอง ก็จะมีทั้งแบบมีข้อมูล ไม่มีข้อมูล เพื่อดูว่า Recycler View ของเจ้าของบล็อกทำงานได้ถูกต้องหรือป่าว

FakeNetwork.java
...

private static OrderDetail createFakeOrderDetail() {
    String[] fakeJsonList = {
            "{\"food_list\":[{\"order_name\":\"Chicken\",\"amount\":2,\"price\":400},{\"order_name\":\"Egg\",\"amount\":24,\"price\":120}],\"book_list\":[{\"ISBN\":\"9780804139038\",\"book_name\":\"The Martian: A Novel\",\"author\":\"Andy Weir\",\"publish_date\":\"11 February 2014\",\"publication\":\"Broadway Books\",\"price\":314,\"pages\":384},{\"ISBN\":\"9781449327972\",\"book_name\":\"Embedded Android: Porting, Extending, and Customizing\",\"author\":\"Karim Yaghmour\",\"publish_date\":\"12 March 2013\",\"publication\":\"O'Reilly Media, Inc.\",\"price\":475,\"pages\":412},{\"ISBN\":\"9780545229937\",\"book_name\":\"The Hunger Games\",\"author\":\"Suzanne Collins\",\"publish_date\":\"1 September 2009\",\"publication\":\"Scholastic Inc.\",\"price\":279,\"pages\":384}],\"music_list\":[{\"artist\":\"Green Day\",\"album\":\"American Idiot\",\"release_date\":\"8 September 2004\",\"track\":9,\"price\":330}]}",
            "{\"food_list\":[],\"book_list\":[],\"music_list\":[]}",
            "{\"food_list\":null,\"book_list\":null,\"music_list\":null}",
            "{ }",
            "",
            "null"
    };
    int index = new Random().nextInt(fakeJsonList.length);
    Log.e("FakeNetwork", "Response with data set " + index);
    return new Gson().fromJson(fakeJsonList[index], OrderDetail.class);
}

        แอบใส่ Log ไว้ให้ด้วย จะได้รู้ว่าสุ่มได้ข้อมูลชุดไหน

        อย่าไปใส่ใจโค้ดตรงส่วนนี้มากนัก เพราะเจ้าของบล็อกแค่จำลองมันขึ้นมาเพื่อทดสอบการทำงานของ Recycler View เฉยๆ

เพิ่มโค้ดสำหรับกรณีที่ไม่มีข้อมูล

        เมื่อต้องการเพิ่มอะไรใน Adapter สิ่งที่ต้องทำมีดังนี้

        • สร้าง Layout XML
        • สร้าง View Holder
        • เพิ่ม Type ใหม่
        • สร้าง Model ของ View Holder โดยเป็น Type ตัวใหม่
        • เพิ่ม Type และ View Holder ตัวใหม่ใน onCreateViewHolder และ onBindViewHolder ของ Adapter
        • สร้าง Model ใน Converter หรือ Activity เมื่อเข้าเงื่อนไขที่ต้องการ


        สิ่งแรกที่ต้องทำนั้นไม่ใช่ส่วนของ Logic แต่จะเป็น Layout XML เพราะจะต้องกำหนดก่อนว่าถ้าไม่มีข้อมูลจะต้องแสดง Layout ยังไง

         ซึ่งเจ้าของบล็อกก็สมมติว่ามันจะต้องแสดงแบบนี้ละกันเนอะ


         ดังนั้นเจ้าของบล็อกก็จะสร้าง Layout ขึ้นมาแบบนี้

strings.xml
<resources>

    ...
    
    <string name="no_order_selected">No order selected</string>

</resources>


view_no_order.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="@dimen/margin_padding_small"
    android:layout_marginEnd="@dimen/margin_padding"
    android:layout_marginStart="@dimen/margin_padding"
    android:layout_marginTop="@dimen/margin_padding_small"
    android:background="@color/angel_white"
    android:orientation="horizontal"
    android:padding="@dimen/margin_padding">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="@string/no_order_selected"
        android:textColor="@color/space_gray"
        android:textSize="@dimen/text_size" />

</LinearLayout>


        อ้อ แล้วก็อย่าลืมสร้าง View Holder ด้วยนะ ถึงแม้ว่าจะไม่ต้อง Binding View ก็ตาม

NoOrderViewHolder.java
import android.support.v7.widget.RecyclerView;
import android.view.View;


public class NoOrderViewHolder extends RecyclerView.ViewHolder {
    public NoOrderViewHolder(View itemView) {
        super(itemView);
    }
}

        เพิ่ม Type สำหรับกรณีที่ View ตัวใหม่นี้เข้าไปในคลาส OrderDetailType

OrderDetailType.java
public class OrderDetailType {

    ...
    
    public static final int TYPE_NO_ORDER = 9;

}

        เพิ่ม Model สำหรับ No Order Type โดยไม่มีอะไรข้างใน เพราะแสดงแค่ข้อความที่เตรียมไว้ (อย่าลืม Extend จากคลาส BaseOrderDetailItem ล่ะ)

NoOrderItem.java
public class NoOrderItem extends BaseOrderDetailItem {
    public NoOrderItem() {
        super(OrderDetailType.TYPE_NO_ORDER);
    }
}

        แล้วกำหนด View Holder และ Binding ให้เรียบร้อย

OrderAdapter.java
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;


public class OrderAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    ...

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == OrderDetailType.TYPE_USER_DETAIL) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_user_detail, parent, false);
            return new UserDetailViewHolder(view);

        ...

        } else if (viewType == OrderDetailType.TYPE_NO_ORDER) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_no_order, parent, false);
            return new NoOrderViewHolder(view);

        }
        throw new NullPointerException("View Type " + viewType + " doesn't match with any existing order detail type");
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        BaseOrderDetailItem orderDetailItem = orderDetailItemList.get(position);
        
        ...

        } else if (holder instanceof NoOrderViewHolder) {
            NoOrderViewHolder noOrderViewHolder = (NoOrderViewHolder) holder;
            NoOrderItem noOrderItem = (NoOrderItem) orderDetailItem;
            setupNoOrder(noOrderViewHolder, noOrderItem);

        }
    }

    ...

    private void setupNoOrder(NoOrderViewHolder noOrderViewHolder, NoOrderItem noOrderItem) {
        // Nothing to do ...
    }

    ...
}

        คำสั่งที่ของเก่าขอตัดออกไปเพื่อให้เห็นแต่เฉพาะโค้ดที่เพิ่มเข้ามาใหม่นะ

        เพียงเท่านี้ Adapter ของเจ้าของบล็อกก็พร้อมที่จะแสดง Type ตัวใหม่แล้ว ที่เหลือก็แค่กำหนดข้อมูลที่ส่งเข้ามาให้เป็น Type ที่ต้องการเท่านั้นเอง

        ดังนั้นที่ MainActivity เจ้าของบล็อกจึงมีการเช็คเงื่อนไขว่ามีข้อมูล Food, Book และ Music หรืออย่างใดอย่างหนึ่งหรือป่าว ถ้าไม่มีข้อมูลใดๆซักอย่างก็จะแสดงเป็น No Order Type แทนของเก่าที่จะแสดง Section Type และ Order Type

MainActivity.java
public class MainActivity extends AppCompatActivity implements OrderAdapter.OnItemClickListener {
    ...

    private void setOrderDetail(OrderDetail orderDetail) {
        
        ...

        List<BaseOrderDetailItem> orderDetailItemList = new ArrayList<>();
        orderDetailItemList.add(OrderDetailConverter.createUserDetail(name));
        orderDetailItemList.add(OrderDetailConverter.createTitle(yourOrderTitle));
        if (isOrderDetailAvailable(orderDetail)) {
            orderDetailItemList.addAll(OrderDetailConverter.createSectionAndOrder(orderDetail, foodTitle, bookTitle, musicTitle, currency, foodTitleColor, bookTitleColor, musicTitleColor));
            orderDetailItemList.add(OrderDetailConverter.createTitle(summaryTitle));
            orderDetailItemList.addAll(OrderDetailConverter.createSummary(orderDetail, foodTitle, bookTitle, musicTitle, currency));
            orderDetailItemList.add(OrderDetailConverter.createTotal(orderDetail, currency));
            orderDetailItemList.add(OrderDetailConverter.createNotice());
            orderDetailItemList.add(OrderDetailConverter.createButton());
        } else {
            orderDetailItemList.add(OrderDetailConverter.createNoOrder());
        }
        orderDetailItemList.add(OrderDetailConverter.createEmpty());

        ...
    }

    private boolean isOrderDetailAvailable(OrderDetail orderDetail) {
        return orderDetail != null &&
                ((orderDetail.getFoodList() != null && !orderDetail.getFoodList().isEmpty()) ||
                        (orderDetail.getBookList() != null && !orderDetail.getBookList().isEmpty()) ||
                        (orderDetail.getMusicList() != null && !orderDetail.getMusicList().isEmpty()));
    }

    ...
}

        โดยเงื่อนไขในการเช็ค เจ้าของบล็อกได้สร้างเป็น Method อีกตัวไว้ มีชื่อว่า isOrderDetailAvailable เนื่องจากเงื่อนไขค่อนข้างซับซ้อนนิดหน่อย คือคลาส OrderDetail ต้องไม่เป็น null และข้างในนั้นต้องมีข้อมูลอย่างน้อยหนึ่งตัว

        สาเหตุที่ยังใส่ EmptyItem ต่อท้ายอยู่ก็เพราะอยากให้มี Space ว่างๆต่อท้ายอยู่ เวลา Scroll ข้อมูลจะได้ไม่ชิดขอบล่างจนเกินไป (ซึ่งแม้ว่าไม่มีข้อมูลที่แสดง แต่ก็ต้องเผื่อกรณีจอเล็กและแสดงผลในแนวนอน)

         เสร็จเรียบร้อยจ้า


        ก็ยังออกมาไม่เหมือนดีไซน์เป๊ะๆอยู่ดี เพราะว่าก๊อป Layout ตัวเก่ามาแก้ไข ฮาๆ อย่าถือสากันเลยนะ

        แต่ก็จะเห็นว่ารูปแบบที่เจ้าของบล็อกใช้นั้น ถึงแม้ว่าจะมีการแก้ไขหรือเพิ่มเติมอะไร สิ่งที่เกิดขึ้นใน Adapter ก็จะยังคงเรียบง่ายอยู่เหมือนเดิม และความซับซ้อนก็จะไปอยู่ในคลาส Converter ที่เจ้าของบล็อกแยกขึ้นมาอีกชุดหนึ่งเพื่อทำหน้าที่นี้โดยเฉพาะ ซึ่งวิธีนี้จะทำให้ Activity กับ Converter เป็นตัวกำหนดรูปแบบการแสดงผลของ Adapter แทน

        การ Implement ในรูปแบบนี้จะสะดวกมากเมื่อมีการแก้ไข Logic ในการแสดงผล ยกตัวอย่างเช่น อยากให้แสดงผลแบบนี้แทน


        ที่ต้องทำก็แค่แก้ไขเงื่อนไขในการแสดงผลตามที่ต้องการนั่นเอง

MainActivity.java
public class MainActivity extends AppCompatActivity implements OrderAdapter.OnItemClickListener {
    
    ...

    private void setOrderDetail(OrderDetail orderDetail) {
        ...

        List<BaseOrderDetailItem> orderDetailItemList = new ArrayList<>();
        orderDetailItemList.add(OrderDetailConverter.createUserDetail(name));
        if (isOrderDetailAvailable(orderDetail)) {
            orderDetailItemList.add(OrderDetailConverter.createTitle(yourOrderTitle));
            orderDetailItemList.addAll(OrderDetailConverter.createSectionAndOrder(orderDetail, foodTitle, bookTitle, musicTitle, currency, foodTitleColor, bookTitleColor, musicTitleColor));
            orderDetailItemList.add(OrderDetailConverter.createTitle(summaryTitle));
            orderDetailItemList.addAll(OrderDetailConverter.createSummary(orderDetail, foodTitle, bookTitle, musicTitle, currency));
            orderDetailItemList.add(OrderDetailConverter.createTotal(orderDetail, currency));
            orderDetailItemList.add(OrderDetailConverter.createNotice());
            orderDetailItemList.add(OrderDetailConverter.createButton());
        } else {
            orderDetailItemList.add(OrderDetailConverter.createTitle(yourOrderTitle));
            orderDetailItemList.add(OrderDetailConverter.createNoOrder());
            orderDetailItemList.add(OrderDetailConverter.createTitle(summaryTitle));
            orderDetailItemList.add(OrderDetailConverter.createTotal(orderDetail, currency));
        }
        orderDetailItemList.add(OrderDetailConverter.createEmpty());

        ...
    }

    ...
}

        และเพิ่มโค้ดนิดหน่อยที่คำสั่ง getTotalPrice ใน OrderDetailConvert เพราะของเดิมนั้น เจ้าของบล็อกไม่ได้ดัก Null ไว้ (แอบรู้สึกบาปเล็กน้อย)

OrderDetailConverter.java
public class OrderDetailConverter {

    ...

    private static int getTotalPrice(OrderDetail orderDetail) {
        int totalPrice = 0;
        if (orderDetail != null) {
            totalPrice += getTotalFoodPrice(orderDetail.getFoodList());
            totalPrice += getTotalBookPrice(orderDetail.getBookList());
            totalPrice += getTotalMusicPrice(orderDetail.getMusicList());
        }
        return totalPrice;
    }

    ...
}

        เป็นอันเสร็จ


        ซึ่งผู้ที่หลงเข้ามาอ่านก็สามารถนำไปปรับเปลี่ยนรูปแบบการทำงานได้ตามใจชอบ เช่น อยากจะย้ายส่วนที่เป็น Logic ใน Activity ไปอยู่ใน Converter ทั้งหมดก็ย่อมทำได้เช่นกัน

        สำหรับผู้ที่หลงเข้ามาอ่านคนใดที่อยากจะดูโค้ดในตอนที่ 3 นี้ ก็สามารถไปดาวน์โหลดได้ที่ Lovely Recycler View - Branch part_3_ending [Github]

        แต่เอาเข้าจริงเจ้าของบล็อกก็ยังรู้สึกว่ามันไม่ค่อยซับซ้อนซักเท่าไรเลยเนอะ เพราะงั้นในบทความหน้ามาทำให้มันยุ่งยากขึ้นมากกว่านี้กันเถอะ!!