Day 9 : Butter Knife

Day 9 : Butter Knife Cover Image

สวัสดีครับ บทความนี้เป็นบทความที่ 9 แล้วนะครับ ที่ผมจะมาเขียน ในซีรีย์ Learn 30 Android Libraries in 30 days

สำหรับบทความทั้งหมด อ่านได้จากด้านล่างครับ

สำหรับวันนี้ขอนำเสนอเรื่อง Butter Knife ครับ

Butter Knife คืออะไร?

Butter Knife , View “injection” library for Android มันคือ Library สำหรับการ Inject View (Inject แปลตรงๆก็คือการฉีด) ให้เราอัตโนมัติ โดย Butter Knife จะทำหน้าที่หน้า View ให้เราเอง และทำการ cast View ให้เราเอง

สำหรับใครที่ไม่เข้าใจเรื่อง Injection แนะนำให้อ่าน Inversion of Control หรือ Dependencies Injection ประกอบครับ

ยอมรับเลยว่าผมไม่เคยใช้ Annotation บน Android มาก่อนเลย นอกจาก @Override กับ @Deprecated นะ :D เมื่อลองใช้ Butter Knife แล้ว ทำให้หวนนึกถึงอดีตที่เคยเขียน Java Spring ครับ คุ้นเคยกับการใช้ @Autowired, @Qualifier, @RequestMapping, @Service, @Controller อุ้ย เยอะแยะ พูดแล้วก็คิดถึง ฮ่าๆ แต่ว่าหากใครไม่คุ้นเคยกับ Annotation เมื่อมาลองจับ Butter Knife ครั้งแรก คิดว่าน่าจะไม่ยากเท่าไหร่นะครับ ลองอ่านบทความนี้ดูครับ เผื่ออาจจะสนใจขึ้นมา

Installation

ขั้นตอนการติดตั้ง Library ตัวนี้ ทำได้ไม่ยากครับ ดาวน์โหลดไฟล์ .jar จากที่นี่ butterknife-5.1.1.jar หรือจะเป็น gradle ก็เปิดไฟล์ build.gradle แล้วเพิ่ม dependencies นี้ลงไป

dependencies {
    compile 'com.jakewharton:butterknife:5.1.1'
}

Getting Started

โปรเจ็คนี้ผมใช้ไฟล์ 2 ไฟล์ครับ คือ ButterKnifeActivity.java และ activity_butter_knife.xml สำหรับเลเอาท์

ไฟล์ activity_butter_knife.xml มี TextView 2 อัน และ Button 1 อัน ดังนี้

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:padding="16dp"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:id="@+id/greeting"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:layout_below="@id/greeting"
        android:id="@+id/name"/>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/button_click"
        android:paddingLeft="32dp"
        android:paddingRight="32dp"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:id="@+id/button_click"/>
</RelativeLayout>

ใส่ references ให้ String นิดนึง (ไม่ชอบ hard code)

ไฟล์ res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="button_click">Click</string>
</resources>

@InjectView

โดยปกติเวลาเราๆเขียนแอพ เวลาเราจะเชื่อม View ที่เราทำไว้ใน Layout ปกติเราจะใช้โค๊ดแบบนี้ใช่มั้ยครับ?

public class ButterKnifeActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.day9_activity_butter_knife);

        TextView greeting = (TextView) findViewById(R.id.greeting);
        TextView name = (TextView) findViewById(R.id.name);
        Button buttonClick = (Button) findViewById(R.id.button_click);
        buttonClick.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });
    }

}

ที่นี้ ถ้าเราใช้ Butter Knife เราใช้โค๊ดแค่นี้

public class ButterKnifeActivity extends ActionBarActivity {

    @InjectView(R.id.greeting) TextView mGreeting;
    @InjectView(R.id.name) TextView mName;
    @InjectView(R.id.button_click) Button mButtonClick;

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

อธิบายคือ Butter Knife จะใช้ annotation @InjectView(view_id) โดยมี parameter เป็น id ของ View ที่เราต้องการหา ตามด้วย View และตัวแปรที่ต้องการครับ อย่างเช่น

@InjectView(R.id.greeting) TextView mGreeting;

มันก็คือการหา View ที่ชื่อ greeting ในไฟล์ day9_activity_butter_knife จากนั้นก็จะ cast มาใส่ TextView ของเราที่ชื่อ mGreeting

ฉะนั้น @InjectView() : ก็คล้ายๆกับการ findViewById() ส่วนการ cast มาใส่ TextView ก็เหมือนกับการใช้ (TextView) findViewById();

อ้อแล้วก็ ButterKnife.inject(this); อันนี้จะขาดไม่ได้เลย มันคือการสั่งให้ Butter Knife inject View ในเลเอาท์นี้ให้เราหน่อยนะ เปรียบเสมือนเราลืมทำการ setContentView() นั่นแหละ

@OnClick

หลายคนอาจจะสงสัย ถ้าเรา Inject View ได้แล้ว แล้วเราสามารถจะ setListener ให้กับ Button ได้มั้ย? คำตอบคือได้ครับ โดยใช้ @OnClick อย่างเช่น ปกติโค๊ดเราเป็นแบบนี้

 Button buttonClick = (Button) findViewById(R.id.button_click);
buttonClick.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // sayHello();
    }
});

เมื่อเราใ้ช้ Butter Knife จะเหลือแค่นี้

@InjectView(R.id.button_click) Button mButtonClick;

@OnClick(R.id.button_click)
public void sayHello() {

}

อธิบายเพิ่มเติมคือ เราสร้างเมธอดขึ้นมาใหม่ สมมติเมธอดเอาไว้โชว์ Toast จากนั้นใช้ @OnClick() แล้วส่ง parameter เป็นชื่อ view_id ไป ตัว Butter Knife มันก็จะไปทำการ setListerner() ให้เราเอง

ลองเปรียบเทียบดูครับ อันบนกับอันล่าง อันไหนน่าใช้กว่ากัน :D

@InjectViews

เห็นหัวข้ออย่าสับสนนะครับ เขียนผิดหรือเปล่า ทำไมเขียน @InjectViews ซ้ำ ที่จริงไม่ซ้ำกันนะครับ มี s กับ ไม่มี s สำหรับ @InjectViews อันนี้ เราสามารถให้มัน inject ทีเดียว หลายๆ View ได้เลย อย่างเช่น กรณีที่เรามี TextView, Button ในหน้าเลเอาท์นั้นๆ เยอะมากๆ ต้องมา @InjectView() ทุกๆอัน ก็ชักช้า เราสามารถทำแบบนี้ได้

@InjectViews({R.id.greeting, R.id.name})
List<TextView> mTextViews;

mTextViews.get(0).setText("Hello");
mTextViews.get(1).setText("Jane Doe");

โดยการส่ง parameter เป็น array ที่มีไอดีอยู่ จากนั้น ตามด้วย List<TextView> เวลาเรียกใช้ก็แค่ระบุ index ของ List

findById

สุดท้ายครับ findById ของ Butter Knife จะทำหน้าที่เหมือนตัว findViewById() แบบปกติครับ แต่ว่าของ Butter Knife จะมีการ casting ให้เราอัตโนมัติครับ เช่น

TextView greeting = (TextView) findViewById(R.id.greeting);
Button buttonClick = (Button) findViewById(R.id.button_click);

แบบนี้ เราต้องมา cast greeting จาก View ไปเป็น TextView และ buttonClick ไปเป็น Button เอง แต่ถ้าใช้ Butter Knife จะเป็นแบบนี้

TextView greeting = ButterKnife.findById(this, R.id.greeting);
Button buttonClick = ButterKnife.findById(this, R.button_click);

โดย this หมายถึงหา View ใน Activity ของเรานะครับ

สรุป

จากที่เขียนมาทั้งหมดก็ยังเป็นแค่ Basic อยู่ ใครอยากรู้รายละเอียดแบบลึกขึ้น ก็แนะนำอ่านจากต้นฉบับเลยครับ Butter Knife

หลังจากได้ทดลองใช้ Butter Knife แล้วรู้สึกว่าก็โอเคนะครับ ใช้งานค่อนข้างง่าย ยอมรับเลยว่าไม่เคยใช้มาก่อนเลย นี้เพิ่งใช้ครั้งแรก แล้วก็เขียนบทความเลย อาจจะเพราะเคยใช้ annotation บน Spring มาก่อนรึเปล่าไม่แน่ใจนะครับ

รู้สึกว่า มันทำให้โค๊ดดูสะอาดตาขึ้นเยอะครับ แต่จริงๆ ผมก็ยังชอบใช้ findViewById() นะ ก่อนที่จะมาลอง Butter Knife ผมได้เห็นอันที่คล้ายๆกันแล้วเช่น Dagger, Roboguice, Android Annotations เดี่ยวอาจจะลองเล่น แล้วเอามาเปรียบเทียบกันดูครับ ว่าอันไหนใช้ง่าย หรือว่ามีอันไหนมีฟังค์ชันที่พิเศษที่อันอื่นไม่มี ไว้คอยติดตามบทความถัดไปนะครับ ขอบคุณครับ

สุดท้าย Source Code อัพเดทล่าสุด

Chai

Chai Phonbopit : Developer แห่งหนึ่ง • ผู้ชายธรรมดาๆ ที่ชื่นชอบ Node.js, JavaScript และ Open Source มีงานอดิเรกเป็น Acoustic Guitar และ Football

บทความล่าสุด