Day 10 - Android Annotations

สวัสดีครับ บทความนี้เป็นบทความที่ 10 แล้วนะครับ ที่ผมจะมาเขียน ในซีรีย์ Learn 30 Android Libraries in 30 days
สำหรับบทความทั้งหมด อ่านได้จากด้านล่างครับ
- Day 1 : AndroidStaggered Grid
- Day 2 : Paralloid
- Day 3 : Retrofit
- Day 4 : SwipeRefreshLayout
- Day 5 : Android GraphView
- Day 6 : Holo Color Picker
- Day 7 : Android Async Http
- Day 8 : Crashlytics
- Day 9 : Butter Knife
- Day 10 : Android Annotations
- Day 11 : DateTimePicker
- Day 12 : Circular Progress Button
- Day 13 : ViewPager
- Day 14 : ViewPagerIndicator
- Day 15 : FadingActionBar
- Day 16 : AutofitTextView
- Day 17 : SwipeListView
- Day 18 : ShowcaseView
- Day 19 : GreenDAO
- Day 20 : AndroidViewAnimation
- Day 21 : ActiveAndroid
- Day 22 : Twitter4J
- Day 23 : ListViewAnimations
- Day 24 : AndEngine
- Day 25 : EazeGraph
- Day 26 : Cardslib
- Day 27 : AdapterKit
- Day 28 : WeatherLib
- Day 29 : FlatUI
- Day 30 : Android Firebase
สำหรับวันนี้ขอนำเสนอเรื่อง Android Annotations ครับ
Android Annotations คืออะไร?
Android Annotations มันคือ Android Library ที่จะช่วยให้คุณพัฒนาแอพ Android ได้เร็วขึ้น อ่านง่ายขึ้น โค๊ดเป็นระเบียบ ตรงตามหลักการ Dependency Injection เลย โดยปกติแล้ว เวลาเราเขียนโค๊ด Android เบื่อมั้ยที่ต้องมี View เยอะๆ แล้วต้องมาทำ findViewById()
ทุกๆครั้งๆ มี Button ที่ต้องมานั่ง setOnClickListener()
แล้วถ้ามันมีวิธีที่สะดวกสบายกว่านั้น แถมโค๊ดสะอาดตา ฟังดูน่าสนใจใช่มั้ยครับ?
นั่นแหละ จึงเป็นสาเหตุว่า ทำไมถึงมี Android Annotations ขึ้นมา อ่านรายละเอียดเพิ่มเติมได้ที่ Android Annotations Wiki
Installation
สำหรับวิธีการติดตั้ง ผมจะพูดถึงการติดตั้งด้วย build.gradle
บน Android Studio นะครับ หากเป็น Eclipse คิดว่าน่าจะแค่ดาวน์โหลด androidannotations-bundle-3.0.1.zip จากนั้นก็อปไฟล์ jar มาวาง แล้วก็ Add Built Path น่าจะได้แล้ว
วิธีการ config กับ Android Studio เปิดไฟล์ build.gradle
เพิ่ม โค๊ดนี้ลงไป เพื่อให้มันรู้จัก Plugin ชื่อ android-apt
buildscript { repositories { mavenCentral() } dependencies { classpath 'com.neenbedankt.gradle.plugins:android-apt:1.+' }}
จากนั้นเพิ่ม apply plugin: 'android-apt'
ต่อท้าย com.androdi.application
ดังนี้
apply plugin: 'com.android.application'apply plugin: 'android-apt'
ต่อมาเพิ่มโค๊ดนี้ลงไป
apt { arguments { androidManifestFile variant.processResources.manifestFile resourcePackageName "com.devahoy.learn30androidlibraries" }}
เพื่อให้ android annotation ทำการ gen ไฟล์ที่อยู่ใน package name ของเรา (สิ่งที่ต้องเปลี่ยนคือเปลี่ยน resourcePackageName
เป็นชื่อ package name ของคุณเองนะครับ)
ต่อมาเพิ่ม dependencies ของ Android Annotations ลงไป
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:20.+'
apt "org.androidannotations:androidannotations:3.0+" compile "org.androidannotations:androidannotations-api:3.0+"
}
ไฟล์ build.gradle
จะได้ดังนี้
buildscript { repositories { mavenCentral() } dependencies { classpath 'com.neenbedankt.gradle.plugins:android-apt:1.+' }}
apply plugin: 'com.android.application'apply plugin: 'android-apt'
android { compileSdkVersion 20 buildToolsVersion "20.0.0"
defaultConfig { applicationId "com.devahoy.learn30androidlibraries" minSdkVersion 11 targetSdkVersion 20 versionCode 1 versionName "1.0" } buildTypes { release { runProguard true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }}
apt { arguments { androidManifestFile variant.processResources.manifestFile resourcePackageName "com.devahoy.learn30androidlibraries" }}
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:20.+'
apt "org.androidannotations:androidannotations:3.0+" compile "org.androidannotations:androidannotations-api:3.0+"
}
กด Sync Gradle เพื่อทำการโหลด dependencies ทั้งหมด
สุดท้าย ลืมไม่ได้ เปิดไฟล์ AndroidManifest.xml
แล้วเปลี่ยนชื่อ Activity แรกที่จะใช้เป็น Activity หลักเมื่อเปิดแอพขึ้นมา โดยการเพิ่ม underscore(_) ต่อท้ายชื่อ เช่น
<application> <activity android:name=".day10.AnnotationsActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
</application>
ให้กลายเป็น
<application> <activity android:name=".day10.AnnotationsActivity_" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
</application>
เท่านี้ก็เรียบร้อย หากใครเพิ่ม underscore(_) แล้วมันยัง error ว่าหา class นี้ไม่เจอ แสดงว่าท่านยัง setup ไม่ถูกนะครับ ลองไปดูไฟล์ build.gradle
ใหม่ หรือหากอยากลองเปรียบเทียบกับไฟล์ build.gradle ของผมก็นี้ครับ
หรือหากใครคิดว่า setup ยาก ก็ลองโหลดไฟล์ jar มาเลยก็ได้ครับ (ส่วนนี้ผมไม่ได้ลองแฮะ ว่ามันง่ายจริงไหม)
Getting Started
การใช้งาน Android Annotations หลังจากลองเล่นดู รู้สึกว่าคล้ายๆกับ Butter Knife ที่ได้ลองใช้ไปเมื่อวานเลยครับ คอนเซปคล้ายๆกัน แต่รู้สึกว่าตัวนี้จะมีลูกเล่นให้เล่นได้เยอะกว่า ก่อนที่จะเริ่มสร้างโปรเจ็ค เรามาดูกันก่อนว่า Android Annotations มันมีหลักการทำงานอย่างไร ไฟล์ที่จะใช้ มี 2 ไฟล์เช่นเคย AnnotationsActivity.java
และ day10_activity_annotations.xml
สำหรับไฟล์ day10_activity_annotations.xml
จะใช้เลเอาท์อันเดียวกันกับ Day 9 : Butter Knife จะได้ดังนี้
<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>
@EActivity
ตัวแรกเลยคือ @Eactivity
เป็น annotation ที่เอาไว้ generate ตัว Activity ให้เป็นชื่อเดียวกัน แต่ว่ามี underscore(_) เพิ่มมาต่อท้ายที่ชื่อด้วย เช่น
package com.devahoy.learn30androidlibraries.day10;
@EActivity(R.layout.day10_activity_annotations)public class AnnotationsActivity extends ActionBarActivity { ...}
มันจะถูก generate ไปเป็นชื่อเดิม + (_) และถูกเก็บไว้อีกโฟลเดอร์นึง ไปเป็น
package com.devahoy.learn30androidlibraries.day10;
@EActivity(R.layout.day10_activity_annotations)public class AnnotationsActivity_ extends ActionBarActivity { ...}
ทีนี้ก็หายสงสัยหรือยังครับ ว่าทำไมเราต้องกำหนด Activity เริ่มต้น เป็นคลาสที่เติม underscore ด้วย :D
<activity android:name=".day10.AnnotationsActivity_" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> </intent-filter></activity>
ทีนี้ลองมาเปรียบเทียบ การใช้ @EActivity
กับการเขียนแบบธรรมดา
หากใช้ @EActivity
โค๊ดเราก็จะเหมือนแบบด้านบน คือเป็นแบบนี้
package com.devahoy.learn30androidlibraries.day10;
@EActivity(R.layout.day10_activity_annotations)public class AnnotationsActivity extends ActionBarActivity { ...}
เปรียบเทียบได้กับการเขียนธรรมดาแบบนี้
package com.devahoy.learn30androidlibraries.day10;
public class AnnotationsActivity extends ActionBarActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.day10_activity_annotations); }}
สังเกตว่า @EActivity()
มันคือการ setContentView()
นั่นเอง
@ViewById
@ViewById
เป็น annotation สำหรับ Inject View ครับ โดยปกติเราต้องทำการ findViewById()
ให้มัน แบบนี้
public class AnnotationsActivity extends ActionBarActivity {
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.day10_activity_annotations);
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) {
} }); }
}
แต่ถ้าหากใช้ Android Annotation ด้วย @ViewById
ก็จะได้แบบนี้
@EActivity(R.layout.day10_activity_annotations)public class AnnotationsActivity extends ActionBarActivity {
@ViewById(R.id.name); TextView name;
@ViewById TextView greeting;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); }
@Click(R.id.button_click) void onClick() { }}
จะสังเกตได้ว่า เราใช้ @ViewById()
โดยรับ parameter เป็น id ของ TextView ที่เราตั้ง แต่ในขณะเดียวกัน เราไม่ต้องใส่ parameter ให้กับ @ViewById
ก็ได้ แต่ว่าชื่อที่ตั้งในโค๊ด และใน layout ทั้งสองต้องตรงกัน เช่น TextView ในเลเอาท์ มี id ชื่อ greeting
ในโค๊ดเราก็ต้องใช้ชื่อ greeting
ส่วน @Click()
ตามด้วย parameter เป็นชื่อ id ของ Button คือการ binding และทำการ setOnClickListener()
ให้กับ Button โดยเราไม่ต้อง setListerner เอง ไม่ต้องสร้าง inner class เองให้ยุ่งยาก ลดความผิดพลาดลงไปได้เยอะครับ
ข้อควรระวัง อย่าทำการ setText()
ให้กับ View เด็ดขาด ไม่เชื่อคุณลอง ทำแบบนี้ โดยพเพิ่ม greeting.setText("Hello I'm John Doe");
ลงไปในเมธอด onCreate()
@EActivity(R.layout.day10_activity_annotations)public class AnnotationsActivity extends ActionBarActivity {
@ViewById(R.id.name); TextView name;
@ViewById TextView greeting;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
greeting.setText("Hello I'm John Doe"); }
@Click(R.id.button_click) void onClick() { }}
ลองรันดู ว่าจะมี error มั้ย?
แน่นอนครับ มันจะเกิด NullPointerException เนื่องจากว่า มันยังไม่ถูก Inject ครับ ซึ่งโดยปกติแล้ว Android Annotation มันจะ generate ไฟล์ต่างๆ และทำการ Inject ในช่วง Compile Time นะครับ
อ้าว แล้วงี้เราจะ setText()
ยังไงละเนี่ย? วิธีแก้ อ่านต่อด้านล่างครับ :D
@AfterView
@AfterView
คือ annotation ที่เอาไว้บอก เมธอดนั้นๆว่า เอ้ย! จะถูก call หลังจาก binding View เรียบร้อยแล้วนะ ( setContentView()
และทำการ findViewById()
เรียบร้อยแล้ว) ฉะนั้น วิธีที่จะ setText ที่ถูกต้อง ต้องเป็นแบบนี้
package com.devahoy.learn30androidlibraries.day10;
import android.os.Bundle;import android.support.v7.app.ActionBarActivity;import android.widget.TextView;import android.widget.Toast;
import com.devahoy.learn30androidlibraries.R;
import org.androidannotations.annotations.AfterViews;import org.androidannotations.annotations.Click;import org.androidannotations.annotations.EActivity;import org.androidannotations.annotations.ViewById;
@EActivity(R.layout.day10_activity_annotations)public class AnnotationsActivity extends ActionBarActivity {
@ViewById(R.id.greeting) TextView greeting;
@ViewById TextView name;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); }
@Click(R.id.button_click) void onClick() { Toast.makeText(this, "Hello " ,Toast.LENGTH_LONG).show(); }
@AfterViews void setGreeting() { greeting.setText("Hello I'm John Doe"); }}
Features อื่นๆ
ตัว Android Annotation มันไม่ได้ทำได้แค่ ด้านบนนะครับ มันยังสามารถที่จะ Inject System, Service รวมถึงไฟล์ Resource ต่างๆ ทำ UI Thread ทำ Background Thread เรียกได้ว่าครอบคลุมเลยก็ว่าได้ ดูเพิ่มเติม
หลักจากที่ผมได้ลองเล่น Annotation ทั้งสองตัว ทั้ง Butter Knife และ Android Annotation แล้วพบว่า โค๊ดมันแลสวยงาม สบายตาขึ้น ง่ายต่อการแก้ไข ปรับปรุงมากครับ คิดว่าโปรเจ็คในอนาคต จะได้ลองพวก Inject View แน่นอน ส่วนเรื่อง Perfomance อันนี้ก็ไม่รู้นะครับ ว่าจะมีผลแค่ไหน เนื่องจากผมก็เป็นแค่ผู้ใช้ธรรมดาคนหนึ่ง ก็อาศัยอ่านจาก Docs เอาแหละครับ
สุดท้ายอยากจะบอกว่า ใน Wiki ยังมีรายละเอียดให้ศึกษาอีกเยอะมากๆครับ สำหรับบทความนี้ก็เป็นแค่การแนะนำ เบื้องต้นเท่านั้นครับ เพราะว่าผมเพิ่งลองใช้งานเมื่อกี้เอง เรียกได้ว่า ทำไป เขียนรีวิวไป มากกว่า :D
- Authors
-
Chai Phonbopit
เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust