24 May 2017

สรุปสาระสำคัญจากงาน Google I/O 2017 ที่นักพัฒนาแอนดรอยด์ไม่ควรพลาด

Updated on

        ในที่สุดงาน Google I/O 2017 ก็ได้เปิดตัวและจบงานไปได้ด้วยดีพร้อมกับเรื่องราวใหม่ๆ เทคโนโลยีใหม่ๆที่ผุดขึ้นมาในงานนี้อย่างมากมาย ถึงแม้ว่าในปีนี้ทาง Google จะโฟกัสไปในด้าน AI อย่างเห็นได้ชัด แต่ก็ต้องบอกเลยว่าฝั่งแอนดรอยด์ก็มีอะไรใหม่ๆเจ๋งๆเพิ่มเข้ามาเช่นกัน

        และนี่คือหัวข้อต่างๆที่เกี่ยวข้องกับนักพัฒนาแอนดรอยด์ที่หยิบมาพูดถึงในบทความนี้ครับ

        • Kotlin
        • Java 8
        • Android Studio 3.0
        • Architecture Components
        • Android O
        • Android Support Library
        • Dynamic Animation
        • Google's Maven Repository
        • Firebase
        • Instant Apps
        • Device Catalog

Welcome Kotlin

        ในที่สุด Google ก็ได้ประกาศให้ Kotlin เป็น 1st Class Citizen หรือก็คือประกาศรองรับ Kotlin อย่างเป็นทางการแล้ว เรียกได้ว่าเป็นการเปิดตัวที่ทำให้ทุกคนภายในงาน (และทั่วๆโลก) ร้องเฮกันลั่นเลยทีเดียว


        Kotlin เป็นภาษาโปรแกรมมิ่งที่ทาง Jetbrains (Android Studio ทุกวันนี้ก็พัฒนามาจาก IntelliJ IDEA ของ Jetbrains น่ะแหละ) พัฒนาขึ้นมาเพื่อครอบการทำงานของภาษา Java อีกทีหนึ่ง (เหมือน Groovy หรือ Scala) โดยมี Syntax ที่เรียบง่ายและสวยงามกว่า Java มาก รวมไปถึงความสามารถต่างๆที่ Java ไม่มี (หรือมีในเฉพาะ Java เวอร์ชันใหม่ๆเท่านั้น)


        ขอเกริ่นถึง Kotlin แค่นี้ก็พอละกัน ที่เหลือลองไปหาอ่านกันต่อเองนะ

        ซึ่งในทุกวันนี้ Kotlin ก็ถูกนำไปใช้งานแทนภาษา Java บน Platform ต่างๆอยู่บ้างพอสมควร รวมไปถึงคนที่ Google ด้วยเช่นกัน ซึ่งฝั่งแอนดรอยด์ก็มีนักพัฒนาหลายๆคนที่นำ Kotlin ไปใช้งานกันเรียบร้อยแล้วเช่นกัน

Kotlin เข้ามาเป็น 1st Class Citizen, แล้วไงต่อ?

        อย่างแรกเลยก็คือ Plugin ของ Kotlin จะ Built-in ไว้ใน Android Studio (เวอร์ชัน 3.0 ขึ้นไป) ให้เลย ไม่ต้องไปนั่งติดตั้งเพิ่มเองแล้ว และจะรองรับการทำงานบน Android Studio ได้สมบูรณ์ยิ่งขึ้น อย่างเช่น สามารถสร้างโปรเจคแอนดรอยด์ที่เป็น Kotlin ได้เลย ไม่ต้องนั่งสร้างใหม่เองทุกครั้งแล้ว


        ต่อมาก็คือ Google จะเข้ามาเป็น Partner กับทาง JetBrain เพื่อช่วยพัฒนา Kotlin ให้ดียิ่งขึ้น และมีแผนที่จะย้าย Kotlin เข้ามาเป็นองค์กรไม่แสวงผลกำไร (Non-profit Foundation) รวมไปถึง Documentation ใน Android Developer ก็จะมีภาษา Kotlin ด้วย

อนาคตที่สดใสรอเราอยู่~

        ในที่สุดก็ได้คำตอบชัดเจนจาก Google เสียที ถึงอนาคตของการนำ Kotlin มาใช้ในการพัฒนาแอปฯ เพียงเท่านี้ผู้ที่หลงเข้ามาอ่านไม่ต้องกังวลอีกต่อไปว่า Kotlin จะเลิก Support กลางคันมั้ย หรือไม่รองรับการทำงานบางอย่างบน Android Studio มั้ย

        ซึ่งถ้าผู้ที่หลงเข้ามาอ่านคนใดยังไม่เคยลองเรียนรู้ Kotlin มาก่อน ขอแนะนำว่าให้ลองดูซักครั้งครับ แล้วจะรู้ว่าตกหลุมรักความสวยงามของภาษา Kotlin แบบที่หลายๆคนเค้าพูดถึงกันนั้นเป็นอย่างไร :D

        และถ้ากำลังตัดสินใจจะเอามาใช้ใน Production ก็เริ่มได้เลยครับ เพราะเอาเข้าจริง Kotlin นั้นรองรับการทำงานควบคู่ไปกับ Java ได้อยู่แล้ว รวมไปถึงรองรับการ Convert จากโค้ด Java มาเป็น Kotlin ด้วย ดังนั้นความเสี่ยงในการ Migrate จาก Java มาเป็น Kotlin จึงน้อยมาก แต่ก็ต้องเรียนรู้เพื่อให้เข้าใจอย่างถ่องแท้ด้วยนะครับ

รองรับการใช้งาน Java 8 บน Android Studio และ Android O

        สามารถใช้ความสามารถของ Java 8 บน Android Studio ได้เต็มที่ ไม่ว่าจะเป็นพวก Lambda, Method Reference, Type Annotations และอื่นๆอีกมากมาย


        เพื่อไม่ให้เข้าใจผิด บน Android Studio นั้นสามารถใช้งาน Java 8 ได้ก็จริง แต่ก็อย่าลืมว่าบนอุปกรณ์แอนดรอยด์นั้นจะต้องเป็น Android O ขึ้นไปเท่านั้น ถึงจะรองรับคำสั่งของ Java 8 ได้ แต่ก็มีคำสั่งบางคำสั่งของ Java 8 ที่นักพัฒนาสามารถเรียกใช้งานใน Android Studio ได้ แล้ว Compile ให้กลายเป็นคำสั่งที่รองรับกับแอนดรอยด์เวอร์ชันเก่าๆด้วย อย่างเช่น Lambda เป็นต้น

        Java 8 ในมุมมองเจ้าของบล็อกนั้น รู้สึกว่ายังไม่เหมาะแก่การนำไปใช้งานอย่างเต็มรูปแบบ แต่สามารถเลือกความสามารถบางอย่างของ Java 8 ที่ทำงานเฉพาะช่วง Compile Time มาใช้งานได้ก็ดีเหมือนกันนะ ซึ่งจากที่ลองเล่นดู ตอนนี้สามารถใช้ Lambda ได้โดยที่รองรับกับแอนดรอยด์เวอร์ชันเก่าด้วย (เดี๋ยวมัน Compile ให้กลายเป็นโค้ดแบบเดิมๆให้เอง) ไม่ต้องใช้ Retrolambda อีกต่อไป

เปิดตัว Android Studio 3.0

        ถ้าผู้ที่หลงเข้ามาอ่านคนใดใช้ Android Studio ใน Dev Channel ก็จะสังเกตเห็นว่าก่อนหน้านี้ทางทีมงานได้ปล่อยอัปเดตของ Android Studio 2.4 มาให้อยู่พักหนึ่ง แต่ก็ได้เปิดตัวเวอร์ชัน 3.0 ภายในงานแทนซะเลย


        ซึ่ง Android Studio ในเวอร์ชัน 3.0 นี้ก็มาคู่กับ Gradle 4.0 ที่รับประกันในความเร็วที่มากกว่าเวอร์ชันก่อนๆ ซึ่งในเวอร์ชันนี้ก็ฟีเจอร์เยอะเหลือเกิน ไว้เดี๋ยวแตกหน่อเป็นบทความแยกไปอีกทีดีกว่า ขอสรุปฟีเจอร์คร่าวๆที่มีดังนี้

        • รองรับการแสดง Emoji ในโค้ด
        • รองรับ Kotlin
        • เพิ่มความสามารถและแก้ปัญหาของ ConstainLayout ในเวอร์ชันก่อนหน้า
        • ปรับปรุงการแสดง Preview Layout ใน AdapterView ให้ Preview ข้อมูลได้หลากหลายมากขึ้น รวมไปถึงรองรับการ Preview ข้อมูลจากไฟล์ JSON
        • Image Asset รองรับการภาพไอคอนสำหรับ Adaptive Icon (ฟีเจอร์ใหม่ใน Android O)
        • Device File Explorer เป็น Built-in File Explorer ที่พัฒนาขึ้นจาก File Explorer ที่เคยใช้กัน ซึ่งรองรับการ Preview ไฟล์ที่อยู่ในเครื่องโดยตรง
        • Modularization เป็นเครื่องมือที่ช่วยแยกโค้ดและไฟล์ที่มีอยู่ออกไปเป็นอีก Module หนึ่ง ซึ่งมีไว้สำหรับการทำ Instant Apps
        • APK Analyzer รองรับ Mapping.txt แล้ว
        • Android Profiler สามารถแสดงข้อมูลการทำงานได้ละเอียดมากขึ้น
        • ไม่ต้องดาวน์โหลด Repository ของ Google จาก Android SDK Manager อีกต่อไป เพราะ Google ย้ายไปไว้ใน Maven repository ของตัวเองแล้ว
        • เพิ่มความรวดเร็วในการ Build Cache
        • ปรับเปลี่ยนรูปแบบการ Build ของ Gradle เพื่อให้ Build APK ได้ไวขึ้น
        • สามารถเรียกใช้งานฟีเจอร์ของ Java 8 ที่ทำงานในช่วง Compile Time ได้
        • ปรับปรุง Dependency Management ที่เคยเป็นปัญหาวุ่นวายสำหรับโปรเจคขนาดใหญ่ที่มีหลาย Module และมี Build Variant ที่หลากหลาย
        • Android Emulator มี Google Play Store ติดตั้งมาให้ในตัวแล้ว
        • สามารถทำ Proxy ใน Android Emulator ได้แล้ว
        • หน้าต่าง Android Emulator มี Bug Report ให้ในตัว
        • เพิ่มเมนูควบคุมการหมุนของเม็ดมะยม (Rotation Input) สำหรับ Android Wear Emulator
        • Layout Inspector สามารถ Search หา Property ที่ต้องการได้
        • รองรับ WebP และเพิ่มเครื่องมือในการแปลงไฟล์ภาพอย่าง JPG/PNG ให้กลายเป็น WebP

        ในตอนนี้ Android Studio 3.0 ยังเป็น Preview อยู่ ซึ่งต้องติดตั้งแยก (เป็นไอคอนสีเหลือง) ก็อย่าลืมดาวน์โหลดมาลองใช้งานดูครับ เนื่องจากมีฟีเจอร์หลายๆอย่างที่ช่วยให้นักพัฒนาทำงานได้สะดวกขึ้นที่อาจจะต้องใช้เวลาเรียนรู้ซักพัก แต่เชื่อเถอะว่าถ้าใช้เป็นแล้วชีวิตจะดีขึ้นมาก

Architecture Component

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

        อาจจะฟังดูเหมือนง่าย แต่กลับกลายเป็นว่าทุกวันนี้ยังมีแอปฯอีกมากมายที่เขียนโค้ดจัดการกับเรื่องเหล่านี้แบบผิดๆอยู่ สุดท้ายทำให้กลายเป็นแอปฯที่ทำงานได้ไม่ถูกต้อง (และเกิดบั๊กที่เหมือนๆกัน) โดยสาเหตุก็เกิดมาจากการที่นักพัฒนาส่วนใหญ่ไม่ได้เข้าใจการทำงานของ Component เหล่านั้นจริงๆ ก็เลยไม่รู้ว่าจะต้องเขียนโค้ดอย่างไรให้เหมาะสม

        ปัญหานี้เป็นปัญหาเรื้อรังมานานพอสมควร (จากที่คุยมา เหมือนจะเป็นปัญหากันทุกที่) จึงทำให้ทีมแอนดรอยด์พัฒนาสิ่งที่เรียกว่า Architecture Components ขึ้นมา ซึ่งเป็น Component ที่จะช่วยให้พัฒนาแอปฯตามรูปแบบที่ถูกต้องได้ง่ายขึ้น เย้ เย


        โดย Architecture Components จะมีทั้งหมด 4 กลุ่มดังนี้

        • Handling Lifecycles
        • LiveData
        • ViewModel
        • Room Persistence Library

Handling Lifecycles

        นักพัฒนาแอนดรอยด์ทุกคนรู้จักกับคำว่า Lifecycle เพราะเป็นเรื่องพื้นฐานอย่างแรกๆที่นักพัฒนาจะต้องเรียนรู้ แต่ก็ยังมีโค้ดที่ทำให้เกิดบั๊กได้ เพราะว่าลำดับการทำงานของโค้ดใน Lifetcycle ไม่ถูกต้อง

        ยกตัวอย่างเช่น

public class MyActivity extends AppCompatActivity {
    private MyLocationListener locationListener;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        locationListener = new MyLocationListener(this, new MyLocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                // Update UI
            }
        });
    }

    @Override
    public void onStart() {
        ...
        PermissionUtil.requestLocationPermission(new LocationPermissionCallback() {
            @Override
            public void onLocationPermissionCallback(boolean enrolled) {
                if (enrolled) {
                    locationListener.start();
                }
            }
        });
    }

    @Override
    public void onStop() {
        ...
        locationListener.stop();
    }
}

        คำสั่งข้างบนนี้จะดูคุ้นๆตากันหน่อยเนอะ เป็นคำสั่งที่เกี่ยวกับ Location ที่มีการเรียกใช้งานใน onStart() และหยุดใช้งานใน onStop() โดยมีการเช็ค Runtime Permission ด้วย ซึ่งคำสั่งดังกล่าวยังมีช่องโหว่อยู่ เพราะถ้า User ยังไม่ได้ Grant Permission แล้วหมุนหน้าจอหรือย่อแอปฯ ก็จะทำให้ locationListener.stop() ทำงานและกลายเป็นบั๊กตัวเป้งขึ้นมา เนื่องจาก locationListener.start() ยังไม่ได้ทำงาน

        ทีมแอนดรอยด์จึงได้สร้างสิ่งที่เรียกว่า LifecycleOwner และ LifecycleObserver ขึ้นมา ขึ้นมาครับ เพื่อให้คลาสที่ต้องยุ่งเกี่ยวกับ Lifecycle ไปจัดการเองตาม Lifecycle ที่ต้องการซะ

public class MyLocationListener implements LifecycleObserver {
    private boolean enabled = false;
    private Lifecycle lifecycle;
    private MyLocationListener listener;

    public MyLocationListener(Context context, Lifecycle lifecycle, MyLocationListener listener) {
        ...
        this.listener = listener;
        this.lifecycle = lifecycle;
        lifecycle.addObserver(this);
    }

    public void enable() {
        enabled = true;
        if (lifecycle.getState().isAtLeast(STARTED)) {
             // Connect if not connected
         }
    }

    @OnLifecycleEvent(ON_START) 
    public void start() {
        if (enabled) {
            // Connect
        }
    }

    @OnLifecycleEvent(ON_STOP) 
    public void stop() {
        // Disconnect if connected
    }

    @OnLifecycleEvent(ON_DESTROY) 
    public void cleanup() {
        lifecycle.removeObserver(this);
    }
}

        โดยมีเงื่อนไขคือคลาสดังกล่าวจะต้อง Implement LifecycleOwner ไว้ด้วย

        MyLocationListener จะจัดการกับ Lifecycle เองได้ยังไง? ให้ดูที่ Constructor ครับ

public class MyLocationListener implements LifecycleObserver {
    private boolean enabled = false;
    private Lifecycle lifecycle;
    private MyLocationListener listener;

    public MyLocationListener(Context context, Lifecycle lifecycle, MyLocationListener listener) {
        ...
        this.listener = listener;
        this.lifecycle = lifecycle;
        lifecycle.addObserver(this);
    }

    ...
}

        จะเห็นว่ามีการโยนคลาสชื่อว่า Lifecycle เข้ามาด้วย มาจากไหนเดี๋ยวค่อยว่ากัน ซึ่งคลาสตัวนี้จะช่วยให้ MyLocationListener สามารถดัก Lifecycle Event จาก Activity/Fragment ที่เรียกใช้งานได้แล้ว โดยใช้คำสั่ง addObserver(LifecycleObserver lifecycleObserver)

        ถ้าอยากจะให้คำสั่งบางคำสั่งทำงานเมื่อ Activity/Fragment เกิด onStart() จะประกาศผ่าน Annotation แบบนี้ได้เลย

public class MyLocationListener implements LifecycleObserver {
    ...

    @OnLifecycleEvent(ON_START) 
    public void start() {
        // Do something when onStart has called
    }
}

        นอกจากนี้ยังสามารถเช็คสถานะการทำงานของ Lifecycle ผ่านคลาส Lifecycle ได้เลย

public class MyLocationListener implements LifecycleObserver {
    ...

    public void enable() {
        ...
        if (lifecycle.getState().isAtLeast(STARTED)) {
             // Do something after this method called after onStart() was called
         }
    }

    ...
}

        จากตัวอย่างข้างบนคือคำสั่ง enable() จะทำงานก็ต่อเมื่อ Activity/Fragment เรียก onStart() ไปแล้วเท่านั้น

        เมื่อใช้งานเสร็จแล้ว อยากให้เคลียร์ตัวเองทิ้ง ก็ลบคลาสนี้ออกจาก Observer ได้เลย

public class MyLocationListener implements LifecycleObserver {
    ...
    @OnLifecycleEvent(ON_DESTROY) 
    public void cleanup() {
        lifecycle.removeObserver(this);
    }
}

        เฮ้ย! เจ๋งอ่ะ จัดการตัวเองได้ทั้งหมดเลย จากเดิมที่จะต้องใส่คำสั่งเหล่านี้ไว้ใน Activity/Fragment แต่เมื่อใช้ LifecycleOwner เข้ามาช่วย เวลาเรียกใช้งาน MyLocationListener ก็จะกลายเป็นแบบนี้แทน

public class MyActivity extends LifecycleActivity {
    private MyLocationListener locationListener;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        locationListener = new MyLocationListener(this, getLifeCycle(), new MyLocationListener() {
            @Override
            public void onLocationChanged(Location location) {
                // Update UI
            }
        });
        PermissionUtil.requestLocationPermission(new LocationPermissionCallback() {
            @Override
            public void onLocationPermissionCallback(boolean enrolled) {
                if (enrolled) {
                    locationListener.enable();
                }
            }
        });
    }

    // ลาก่อน onStart และ onStart
}

        คลาส MyLocationListener เรียกใช้งานใน onCreate(...) ได้เลย จากนั้นก็ปล่อยให้มันจัดการกับตัวเองตาม Lifecycle โดยอัตโนมัติซะ ในคลาส Activity/Fragment ก็ไม่ต้องมานั่งประกาศ onStart() และ onStop() แล้วยัดคำสั่งไว้ในนั้นอีกต่อไป ดีงามมากมาย

        แต่จุดที่ต้องสังเกตต่อคือตอนเรียกใช้งาน MyLcationListener มีการเรียกคำสั่ง getLifecycle()  เพื่อโยน Lifecycle ของ Activity/Fragment ตัวนั้นๆเข้าไปใน MyLocationListener

        ว่าแต่... คำสั่งนี้มันมีด้วยหรอ? 

        คลาส Activity ปกติที่ใช้งานกันไม่ว่าจะเป็น AppCompatActivity, FragmentActivity หรือ Activity นั้นไม่มีคำสั่งที่ชื่อว่า getLifecycle() อยู่นะครับ ซึ่งจะต้องให้ Activity ตัวนั้นๆสืบทอดจากคลาสที่ชื่อว่า LifecycleActivity แทน ซึ่งเป็นคลาสที่เพิ่มเข้ามาใหม่ใน Architecture Components นั่นเอง โดยสืบทอดมาจาก FragmentActivity อีกทีหนึ่ง

        • สำหรับ Activity ให้ใช้ LifecycleActivity
        • สำหรับ Fragment ให้ใช้ LifecycleFragment
        • สำหรับ Service ให้ใช้ LifecycleService

        อ้าว แต่โปรเจคที่ทำอยู่มันใช้ AppCompatActivity อ่ะ ต้องทำยังไง?

        ก็จะต้อง Implement เอาเองแบบนี้ครับ

import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LifecycleRegistryOwner;
import android.support.v7.app.AppCompatActivity;

public class AppCompatLifecycleActivity extends AppCompatActivity implements LifecycleRegistryOwner {
    private final LifecycleRegistry mRegistry = new LifecycleRegistry(this);

    public AppCompatLifecycleActivity() {
    }

    public LifecycleRegistry getLifecycle() {
        return this.mRegistry;
    }
}

        ไม่ยากเนอะ?

        ถือว่าเป็น Component ที่เจ๋งมากๆ เพราะโค้ดที่ต้องจัดการกับ Lifecycle เป็นอะไรที่วุ่นวายมากๆ ถ้าเรียกใช้เยอะๆก็จะเห็นคำสั่งหลายๆบรรทัดอยู่ใน onResume(), onStart(), onPause(), onStop() และ onDestroy() อยู่เสมอ แต่พอใช้ LifecycleOwner และ LifecycleObserver เข้ามาช่วยก็จะทำให้โค้ดแยกออกจากกันได้มากขึ้น ไม่ต้องสนใจว่าคลาสนั้นๆจะยุ่งกับ LIfecycle ยังไง แค่เรียกใช้ในตอนแรกเท่านั้นก็พอ จบ

        แต่สุดท้ายแล้ว Lifecycle ก็ยังเป็นเรื่องสำคัญที่นักพัฒนาต้องเรียนรู้และเข้าใจนะ ไม่ใช่ว่าพอมี LifecycleOwner กับ LIfecyclerObserver แล้วจะไม่ต้องเรียนรู้ Lifecycle อีกต่อไป เพราะสุดท้ายแล้ว ทั้งคู่ก็เกิดมาเพื่อให้นักพัฒนาไปสร้างคลาสครอบคำสั่งต่างๆที่ต้องยุ่งกับ Lifecycle เองอยู่ดี ดังนั้นถ้าไม่เข้าใจ Lifecycle ตั้งแต่แรก ก็ไม่ช่วยให้บั๊กลดน้อยลงหรอกนะ :D

LiveData

        Live Data เป็น Data Holder ที่ทีมแอนดรอยด์ออกแบบมาให้ทำงานตาม Lifecycle ของ Activity/Fragment ที่เรียกใช้งานได้ ซึ่งออกแบบมาให้มีลักษณะเป็น Observable ซึ่งรูปแบบการทำงานจะต่อยอดมาจากเรื่อง Handling Lifecycles อีกทีหนึ่ง

        LiveData เอาไว้ใช้งานกับคลาสใดๆก็ตามที่ต้องมีการอัปเดตค่าอย่างต่อเนื่องและต้องส่งไปให้ Activity/Fragment (Location Service หรือ Accelerometer เป็นต้น) โดยปกติจะต้องใช้ Listener หรือ Delegate ในการส่งค่า แต่สำหรับ LiveData จะถูกสร้างมาให้ส่งค่าผ่าน Observable ครับ (ถ้าเคยใช้ RxJava จะคุ้นเคยกันดี)

        ข้อดีหลักๆของ LiveData คือสามารถทำงานร่วมกับ Lifecycle ได้โดยอัตโนมัติ ซึ่งจะช่วยแก้ปัญหาจากเรื่อง Handling Lifecycles ถ้าลำดับการทำงานไม่ถูกต้องตาม Lifecycle โดย LiveData จะรู้ได้เองว่าถูกเรียกใช้/หยุดเรียกใช้งานตอนไหนของ Lifecycle (ไม่ต้องไปกำหนดเองแล้ว เย้)

        เพื่อให้เห็นภาพง่ายขึ้น จึงขอหยิบ MyLocationListener มาทำให้เป็น LiveData กันต่อเลย

        เพิ่มเติม - ในการเปลี่ยนให้คลาสใดๆก็ตามเป็น LiveData จะต้องเข้าใจก่อนว่าคลาสนั้นมี Data เป็นอะไร ซึ่งใน MyLocationListener จะมี Data เป็นคลาส Location เป็นข้อมูลตำแหน่งพิกัดที่ได้จาก Location Service อีกทีหนึ่ง (จะไม่โยนข้อมูลดังกล่าวออกไปตรงๆ แต่จะใช้ความสามารถของ LiveData แทน)

import android.annotation.SuppressLint;
import android.arch.lifecycle.LiveData;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;

public class MyLocationListener extends LiveData<Location> {
    private LocationManager locationManager;

    public MyLocationListener(Context context) {
        locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
    }

    private LocationListenerAdapter listener = new LocationListenerAdapter() {
        @Override
        public void onLocationChanged(Location location) {
            setValue(location);
        }
    };

    @SuppressLint("MissingPermission")
    @Override
    protected void onActive() {
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 10, listener);
    }

    @SuppressLint("MissingPermission")
    @Override
    protected void onInactive() {
        locationManager.removeUpdates(listener);
    }

    public static class LocationListenerAdapter implements LocationListener {
        @Override
        public void onLocationChanged(Location location) { }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) { }

        @Override
        public void onProviderEnabled(String provider) { }

        @Override
        public void onProviderDisabled(String provider) { }
    }
}

        จะเห็นว่า MyLocationListener สืบทอดคลาสจาก LiveData<T> เรียบร้อยแล้ว โดยกำหนดเป็น LiveData<Location> จากนั้นให้สังเกตที่ onActive() กับ onInactive() ทั้งสองคำสั่งนี้เป็น Override Method ของ LiveData<T> ที่มีไว้กำหนดคำสั่งใดๆก็ตามเพื่อให้เริ่มทำงานและหยุดทำงานครับ

        การเรียกใช้งาน LocationManager จะได้ผลลัพธ์ส่งกลับมาเป็น Listener โดยส่งค่ามาทาง Override Method ที่ชื่อว่า onLocationChanged(Location location) ดังนั้นเจ้าของบล็อกจะต้องส่งค่านี้ออกไปให้ Activity/Fragment ด้วยคำสั่ง setValue(T value)  จะเห็นว่ามันเป็น Generic Type ซึ่งขึ้นอยู่กับที่กำหนดไว้ใน LiveData<T> นั่นเอง

        เวลาเรียกใช้งานจากฝั่ง Activity/Fragment ก็จะไม่ค่อยแตกต่างอะไรจากเดิมมากนัก แต่โค้ดจะกระชับขึ้นอย่างเห็นได้ชัด

public class MyActivity extends LifecycleActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        PermissionUtil.requestLocationPermission(new LocationPermissionCallback() {
            @Override
            public void onLocationPermissionCallback(boolean enrolled) {
                if (enrolled) {
                    callLocationListener();
                }
            }
        });
    }

    private void callLocationListener() {
        MyLocationListener locationListener = new MyLocationListener(this);
        locationListener.observe(this, new Observer<Location>() {
            @Override
            public void onChanged(@Nullable Location location) {
                // Update UI
            }
        });
    }
    ...
}

        แล้ว onActive() กับ onInactive() ทำงานตอนไหน?

        สำหรับ onActive() จะถูกเรียกตอนที่ใช้คำสั่ง observe(...) ซึ่งคำสั่ง observce(...) จะเรียกใช้งานตอน onCreate(...), onStart() หรือ onResume() ก็ได้ จากในตัวอย่างถึงแม้จะเรียกใน onCreate(...) แต่เนื่องจากเรียกในคำสั่ง Runtime Permission จึงทำให้คำสั่งทำงานหลังจากที่ onResume() ทำงานเสร็จเรียบร้อยแล้ว ดังนั้น onActive() จึงถูกเรียกใน onResume()

        และสำหรับ onInactive() จะมีรูปแบบการทำงานขึ้นอยู่กับ onActive() ดังนี้

        • เมื่อ onActive() ทำงานตอน onCreate() จะทำให้ onInactive() ทำงานก่อน onDestroy()
        • เมื่อ onActive() ทำงานตอน onStart() จะทำให้ onInactive() ทำงานก่อน onStop()
        • เมื่อ onActive() ทำงานตอน onResume() จะทำให้ onInactive() ทำงานก่อน onPause()

        ดังนั้นจะเห็นว่านักพัฒนาไม่ต้องมานั่งเสียเวลากับเรื่อง Lifecycle มากนัก เพราะว่า LiveData ได้จัดการไว้ให้อัตโนมัติเรียบร้อยแล้ว

        ให้สังเกตที่คำสั่ง observe(..) ของ LiveData ครับ

void observe(LifecycleOwner owner, Observer<T> observer)

        Argument ตัวแรกสุดที่โยนเข้าไปคือ LifecycleOwner นั่นเอง นั่นหมายความว่า Activity/Fragment จะเรียกใช้งาน LiveData ได้ก็ต้องเป็น LifecycleActivity/LifecycleFragment หรือเป็น Activity/Fragment ที่ Implement LifecycleRegistryOwner เท่านั้นนะ (ดูในเรื่อง Handling Lifecycles)

        ถึงแม้จะดูเหมือนว่า LiveData ผูกการทำงานเข้ากับ LIfecycle ก็จริง แต่ตัวมันไม่ได้ผูกกับ Activity/Fragment โดยตรงเลยซักนิด (มี LifecycleOwner เป็นตัวกลาง) จึงทำให้นักพัฒนาสามารถเขียนเทส LiveData ได้ง่ายขึ้น

ViewModel

        สำหรับ ViewModel ก็จะต่อยอดมาจากเรื่อง Handling Lifecycles กับ LiveData มาอีกทีน่ะแหละ เพราะ LiveData ช่วยจัดการเรื่องการ Lifecycle ก็จริง แต่ไม่ได้จัดการเรื่อง Configuration Changes ให้เลย ดังนั้นถ้าเกิด Configuration Changes ตอนที่ LiveData กำลังทำงานอยู่ ก็จะทำให้ LiveData หยุดและเริ่มทำงานใหม่อีกครั้ง

        อาจจะฟังดูไม่น่าจะมีปัญหาอะไร เพราะ LiveData ก็กลับมาทำงานต่อได้ทันทีนี่นา

        นั่นก็เพราะว่าในตัวอย่าง เจ้าของบล็อกได้ใช้ LiveData กับ Location Service ที่ไม่ได้ซีเรียสว่ามันจะหยุดทำงานหรือป่าว ขอแค่กลับมาทำงานต่อได้ก็พอ

        แล้วถ้าเจ้าของบล็อกใช้ LiveData เพื่อเชื่อมต่อกับ Web Service ล่ะ? ผลที่ได้ก็จะกลายเป็นแบบนี้


        จะเห็นว่าเมื่อแอปฯเปิดขึ้นมาจะทำงานเรียกข้อมูลจาก Web Service ทันที แต่ในระหว่างที่รอข้อมูลส่งกลับมาอยู่นั้น ผู้ใช้ดันหมุนหน้าจอซึ่งทำให้เกิด Configuration Change จึงทำให้ Activity ถูกทำลายแล้วสร้างขึ้นมาใหม่ แล้วก็ไปเรียกข้อมูลจาก Web Service ซ้ำอีกครั้ง

        ไม่ค่อย Make Sense แล้วเนอะ?

        ดังนั้น LiveData จึงไม่ตอบโจทย์สำหรับการทำงานแบบนี้ครับ และนั่นก็คือที่มาของ ViewModel นั่นเองงงงงงงง

        ViewModel? ใช่อันที่มันอยู่ใน MVVM หรือป่าว?

        ใช่ครับ ใช่เลย มันคือ ViewModel ใน MVVM นั่นแหละ หรือในอีกความหมายก็คือ Architecture Components (ในตอนนี้) จะทำให้นักพัฒนาต้องเขียนโค้ดแบบ MVVM นั่นเอง

        //เจ้าของบล็อกใช้ MVP โคตรเสียใจ

        ซึ่งปัญหาเดิมๆที่เกิดขึ้นระหว่างการใช้ MVVM หรือ MVP ในการพัฒนาแอปฯแอนดรอยด์ก็คือจะทำให้ ViewModel (ของ MVVM) หรือ Presenter (ของ MVP) อยู่รอดจาก Configuration Changes ได้ยังไง เพราะนี่คือคำถามยอดนิยมในการหยิบ MVVM และ MVP มาใช้งานกันครับ

        ถ้าอิงจาก MVP ที่เจ้าของบล็อกใช้อยู่ ตามรูปแบบที่เจ้าของบล็อกวางไว้ก็คือปล่อยให้ Presenter แม่มตายไปสิ พอ Activity/Fragment กลับขึ้นมาทำงานใหม่ก็ค่อยให้มันสร้าง Presenter ใหม่ทีหลัง (และต้องรู้ได้ว่าเรียกข้อมูลจาก Web Service อยู่หรือป่าว ซึ่งต้องเขียนโค้ดเยอะพอสมควร)

        แต่ ViewModel ของ Architecture Component นั้นไม่ตายครับ อยู่รอดจาก Configuration Changes ได้ (ขี้โกงสุดๆ) ดังนั้นจึงสามารถทำให้มันกลายเป็นแบบนี้ได้ครับ


        จากเดิมที่ Activity/Fragment เรียกใช้งาน LiveData โดยตรง ก็จะย้าย LiveData ไปไว้ใน ViewModel แทน แล้วให้ Activity/Fragment เรียกใช้งานผ่าน ViewModel อีกทีครับ

public class MyActivityViewModel extends ViewModel {
    private LiveData<User> userData;

    public LiveData<User> getUser(String userId) {
        if (userData == null) {
            userData = webService.getUser(userId);
        }
        return userData;
    }
}

        การเรียกใช้งาน ViewModel จะต้องเรียกผ่านคลาสที่ชื่อว่า ViewModelProviders แบบนี้

public class MyActivity extends LifecycleActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        String userId = ...
        ViewModelProviders.of(this)
                .get(MyActivityViewModel.class)
                .getUser(userId)
                .observe(this, new Observer<User>() {
                    @Override
                    public void onChanged(@Nullable User user) {
                        // Update UI
                    }
                });
        }
    }
    ...
}

        โดยจะเห็นว่า ViewModel ไม่มีการผูกตัวเองเข้ากับ Activity/Fragment เลยซักนิด (ใช้ประโยชน์จาก LifecycleOwner เหมือนเดิม) จึงทำให้เทสได้ง่ายไม่ต่างอะไรกับ LiveData เลย

        และถึงแม้ว่า ViewModel จะรอดจาก Configuration Changes ก็จริง แต่เมื่อ Activity/Fragment นั้นๆถูกทำลาย ViewModel ก็จะหยุดทำงานไปพร้อมๆกัน


        โคตรขี้โกงงงงงงงงงง

        ดังนั้น ViewModel จึงกลายเป็นคำตอบสำหรับนักพัฒนาหลายๆคน แต่การใช้ ViewModel ก็จะทำให้โปรเจคต้องใช้ MVVM ไปโดยปริยาย และโค้ดจะลงตัวอย่างสวยงามมากขึ้นไปอีกถ้าใช้ควบคู่กับ Data Binding Library

Room Persistence Library

        Room เป็น Object Mapping สำหรับ SQLite ที่จะแก้ปัญหาเดิมๆจากการเรียกใช้งาน SQLite ภายในแอปฯซึ่งไม่ค่อยเป็นมิตรต่อนักพัฒนามือใหม่ซักเท่าไรนัก โดยเฉพาะโค้ดตอนเรียกใช้งานที่อ่านยากและผิดพลาดได้ง่าย

SQLiteDatabase db = mDbHelper.getReadableDatabase();

// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
        FeedEntry._ID,
        FeedEntry.COLUMN_NAME_TITLE,
        FeedEntry.COLUMN_NAME_SUBTITLE
};

// Filter results WHERE "title" = 'My Title'
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = { "My Title" };

// How you want the results sorted in the resulting Cursor
String sortOrder = FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";

Cursor cursor = db.query(
        FeedEntry.TABLE_NAME,                     // The table to query
        projection,                               // The columns to return
        selection,                                // The columns for the WHERE clause
        selectionArgs,                            // The values for the WHERE clause
        null,                                     // don't group the rows
        null,                                     // don't filter by row groups
        sortOrder                                 // The sort order
);

        จากตัวอย่างข้างบนนี้จะเห็นว่ามีโค้ดที่ชวนวุ่นวายเต็มไปหมด ทั้งๆที่เป็น SQL Command ง่ายๆแบบนี้

SELECT id, name, subTitle FROM Feed 
WHERE title = 'My Title'
ORDER BY subTitle DESC

        และถ้าเผลอพิมพ์เว้นวรรคใน String ผิด ก็จะทำให้คำสั่งผิดและเกิด Runtime Exception ได้ ซึ่งเจ้าของบล็อกเห็นบ่อยมากโดยเฉพาะนักพัฒนามือใหม่ ซึ่งเป็นหนึ่งในปัญหาที่เรื้อรังมานานเช่นกัน

        จึงเกิดเป็น Library ที่ชื่อว่า Room ขึ้นมา เพื่อให้การเรียกใช้งาน SQLite ด้วยรูปแบบที่กระชับ เข้าใจได้ง่าย และมีการตรวจสอบ SQL Command ทุกครั้งตอน Compile (Compile Time Validation) จึงมั่นใจได้ว่าทุกครั้งที่แอปฯทำงานจะไม่เจอปัญหาจากการใส่ SQL Command ผิดอีกต่อไป

        ซึ่งรูปแบบโค้ดของ Room จะมีลักษณะแบบนี้

@Entity 
public class Feed {
    @PrimaryKey
    int id;
    String title;
    String subTitle;
}

@Dao
interface FeedDao {
    @Query("SELECT id, title, subTitle FROM feed WHERE title = :keyword")
    List<Feed> loadFeed(String keyword);
}

@Database(entities = {Feed.class}, version = 1) 
public abstract class FeedDatabase extends RoomDatabase {
    abstract FeedDao feedDao();
}

        จะเห็นว่ารูปแบบขอโค้ดดูได้ง่ายกว่าแบบเดิมมาก ใช้ DAO เป็นตัวกลางในการเข้าถึง SQLite (ในที่นี้คือคลาส FeedDao) โดยผลลัพธ์ที่ได้จะกลับมาอยู่ในรูปของ POJO ที่นำไปใช้งานได้เลย (ในที่นี้คือคลาส Feed) โดยการเรียกใช้งานนั้นจะเรียกใช้งานผ่าน RoomDatabase ที่ไปครอบ Dao อีกทีหนึ่ง (ในที่นี้คือ FeedDatabase)

        โดยรูปแบบของ SQL Command จะกำหนดไว้ใน Annotation ที่ชื่อว่า @Query ที่ให้นักพัฒนากำหนดคำสั่ง SQL ในนั้นได้โดยตรง จึงทำให้อ่านคำสั่งแล้วรู้ได้ทันทีว่าคำสั่งนั้นคืออะไร และเชื่อมกับ Argument ที่กำหนดไว้ใน Method นั้นๆได้เลย

        ถ้า SQL Command ไม่ถูกต้อง ก็จะแจ้งบอกตอน Compile ให้


        จะ Insert, Update หรือ Delete ก็ทำได้ง่ายๆเช่นกัน

@Query("SELECT * FROM feed WHERE title = :keyword")
List<Feed> loadFeed(String keyword);

@Insert
public void insert(Feed feed);

@Delete
public void deleteAll(Feed... feeds);

@Update
public void updateAll(List<Feed> feeds);

        เวลาเรียกใช้งานก็จะมีลักษณะแบบนี้

FeedDatabase db = Room.databaseBuilder(getContext(), FeedDatabase.class, "feed").build();
List<Feed> feeds = db.feedDao().loadFeed("any_keyword");

        โดยบังคับว่าจะต้องเรียกคำสั่งนี้บน Background Thread เท่านั้น ห้ามเอาไปเรียกบน Main Thread เด็ดขาด เพื่อป้องกันไม่ให้เกิด UI Blocking กับตัวแอปฯ

        แต่ความเจ๋งของ Room ไม่ได้อยู่แค่นั้นครับ เพราะว่า Room นั้นสามารถใช้คู่กับ LiveData ได้ด้วย ดังนั้นเมื่อฐานข้อมูลมีการเปลี่ยนแปลงก็จะทำให้รู้ได้ทันที และ Live Data ก็จะอัปเดตให้เสมอ

@Query("SELECT * FROM feed WHERE title = :keyword")
LiveData<List<Feed>> loadFeed(String keyword);

        เมื่อรองรับ LiveData นั่นก็หมายความว่า สามารถใช้ใน ViewModel ได้นั่นเอง!!

public class FeedViewModel extends ViewModel {
    final LiveData<List<Feed>> feeds;
    public FeedViewModel(FeedDao FeedDao) {
        feeds = feedDao.loadFeed("any_keyword")
    }
    ...
}

        เริ่มเห็นภาพมากขึ้นแล้วใช่มั้ยล่ะ ว่า Component แต่ละส่วนนั้นสัมพันธ์กันยังไง

        นอกจากนี้ยังสามารถใช้กับ RxJava 2 ได้ด้วยนะเออ (ครอบคลุมดีจริง)

@Query("SELECT * FROM feed WHERE title = :keyword")
Flowable<List<Feed>> loadFeed(String keyword);

        ดังนั้นผู้ที่หลงเข้ามาอ่านคนใครที่ใช้ RxJava 2 ก็สามารถเอา Room ไปใช้งานได้ทันที (แต่ถ้าใช้ RxJava 1 อยู่ ก็แนะนำให้ย้ายไปเป็น RxJava 2 แทน)

        จริงๆยังมีอีกเยอะมากเกี่ยวกับ Room ที่แนะนำให้ไปลองเล่นดูเองครับ รับรองว่าถูกใจแน่นอน

Architecture Components ตอบโจทย์เรื่องการเขียนเทสด้วยนะเออ

        จาก Component ทั้ง 4 ตัวที่ทางทีมแอนดรอยด์พัฒนาขึ้นมานั้น เมื่อมองภาพรวมทั้งหมดก็จะพบว่าแต่ละส่วนถูกสร้างขึ้นเพื่อใช้ใน Clean Architecture ที่อยู่ในรูปแบบของ MVVM ครับ


        และที่ขาดไปไม่ได้ในยุคนี้ก็คือเรื่องของการเขียนเทส ซึ่งรูปแบบดังกล่าวถูกออกแบบมาง่ายต่อการเทสอยู่แล้ว (สังเกตดีๆว่า Component ทุกตัวใน Architecture Components ถูกออกแบบมาให้เทสได้อยู่แล้ว) โดยจะแบ่ง Layer จากบนลงล่างเพื่อทำการเขียนเทสตามแบบฉบับของ Clean Architecture

        ถ้าอยากจะเทส UI Controller ก็ให้ Mock View Model แล้วเขียนเทสด้วย Espresso



        ถ้าอยากจะเทส View Model ก็ให้ Mock Repository แล้วเขียนเทสด้วย Android JUnit



        ถ้าอยากจะเทส Repository ก็ให้ Mock Data Sources แล้วเขียนเทสด้วย Android JUnit


ภาพรวมของ Architecture Components

        ถ้าอยากจะลองใช้ Architecture Components ก็สามารถดาวน์โหลดได้จาก Maven Repository เลยครับ

// Google's Maven Repository
maven { url 'https://maven.google.com' }

// For Lifecycles, LiveData and ViewModel
compile "android.arch.lifecycle:runtime:1.0.0-alpha1"
compile "android.arch.lifecycle:extensions:1.0.0-alpha1"
annotationProcessor "android.arch.lifecycle:compiler:1.0.0-alpha1"

// For Room
compile "android.arch.persistence.room:runtime:1.0.0-alpha1"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha1"

        เรียกได้ว่า Architecture Components นั้นเป็น Best Practice ที่ทีมแอนดรอยด์กำหนดขึ้นมาให้ก็ว่าได้ เพื่อที่ว่านักพัฒนาจะได้ไม่ต้องไปเสียเวลานั่งคิดว่าจะใช้ Pattern แบบไหนดี จะต้องเขียนโค้ดยังไงให้เป็นอิสระต่อจากกัน และจะเขียนเทสในแต่ละส่วนยังไง ซึ่งทั้งหมดนี้ทีมแอนดรอยด์คิดมาให้แล้วครับ สามารถ Follow ตาม Guideline ของ Architecture Components ได้เลย

        แต่ก็ไม่ได้หมายความว่านักพัฒนาจะต้องโล๊ะโค้ดทั้งหมดเพื่อเขียนให้ถูกต้องตามที่ทีมแอนดรอยด์วาง Guideline ไว้ให้ เพราะสุดท้ายแล้ว Architecture Components ก็เป็นแค่เพียงหนึ่งทางเลือกในการนำไปใช้งานครับ (ที่เค้าคิดไว้ให้อย่างครอบคลุมแล้ว) ถ้าโปรเจคที่ทำกันอยู่ไม่มีปัญหาอะไร ทำงานได้ดี จัดการกับ Lifecycle ได้ถูกต้อง รับมือกับ Configuration Changes ได้อยู่ไม่มีปัญหา และเขียนเทสได้อยู่แล้ว ก็ทำต่อไปเถอะครับ

        สำหรับรายละเอียเพิ่มเติมของ Architecture Components ลองไปดูกันต่อได้ที่ https://developer.android.com/arch

อัปเดตรายละเอียดฟีเจอร์ใน Android O

        สำหรับ Android O นักพัฒนาหลายๆคนก็คงได้ลองเล่นกับ Developer Preview กันไปแล้ว ซึ่งฟีเจอร์ที่เคยเปิดตัวไปแล้วใน Developer Preview เวอร์ชันแรกจะขอข้ามไปเพื่อโฟกัสเฉพาะฟีเจอร์ใหม่ๆที่เพิ่งมีรายละเอียดเพิ่มเข้ามา สำหรับฟีเจอร์บน Android O ที่เคยเปิดตัวไปแล้วก่อนหน้านี้สามารถไปตามอ่านกันได้ที่ มาดูกันว่า Android O มีอะไรใหม่บ้าง ในแบบฉบับนักพัฒนา


        ซึ่งในปีนี้ก็มีการอัปเดตข้อมูลของฟีเจอร์บน Android O เพิ่มเข้ามา นั่นก็คือ

        • Picture-in-Picture
        • Notification Dots
        • TensorFlow Lite

Picture-in-Picture


        เดิมเป็นฟีเจอร์ที่มีเฉพาะใน Android TV เท่าน้ัน แต่ได้เปิดให้ใช้งานบน Phone และ Tablet ใน Android O แล้ว ซึ่งจะช่วยให้แอปฯยังคงทำงานได้ โดยแสดงผลเป็นแบบ Floating Window ในขณะที่ User ใช้งานอย่างอื่นภายในเครื่องอยู่ อาจจะฟังดูเหมือน Freeform ของ MultiWindow แต่ Picture-in-Picture นั้นจะใช้งานค่อนข้างสะดวกรวดเร็วและสำเร็จรูปมากกว่า โดยที่เน้นไปที่การแสดงวีดีโออย่างการดูหนังหรือวีดีโอคอลกับเพื่อนอยู่ แล้วต้องการทำอย่างอื่นควบคู่ไปด้วย

Notification Dots 

        เป็นการแสดง Notification ใน Home Screen คล้ายๆกับ Notification Badge แต่จะแตกต่างกันตรงที่ Notification Dots จะบอกแค่ว่ามี Notification ในแอปฯนั้นๆ ไม่ได้บอกจำนวนของ Notification


        ผู้ใช้สามารถกดค้างที่ไอคอนแอปฯเพื่อดู Notification ของแอปฯนั้นๆแบบย่อได้ ซึ่งจะแสดงอยู่ในหน้าเดียวกับ Action Shortcut นั่นเอง โดยสีของ Notification Dots จะขึ้นอยู่กับไอคอนของแอปฯนั้นๆ

TensorFlow Lite

        เป็น On-device Machine Learning ที่ทาง Google ใส่ไว้ใน Android O เพื่อให้สามารถทำ Machine Learning อย่างง่ายๆได้ โดยจะมี Library เพื่อเชื่อมต่อใช้งานผ่านแอปฯได้ด้วย (ไม่รู้ว่าตอนนี้มีแล้วหรือยัง) ซึ่งจะช่วยให้แอปฯสามารถนำข้อมูลจากการใช้งานแอปฯไปวิเคราะห์ด้วย Machine Learning เพื่อให้ได้ผลลัพธ์และนำไปใช้งานต่อได้


        ยกตัวอย่างเช่น Smart Text Selection ของ Android O ที่จะวิเคราะห์ข้อความที่ผู้ใช้เลือก และ Suggest แอปฯที่เหมาะสมขึ้นมาทันที ไม่ต้องเสียเวลากด Copy แล้วสลับแอปฯเพื่อกด Paste

        เห็นมั้ย AI เริ่มเข้ามามีบทบาทสำคัญมากแค่ไหน

เปิดให้ทดสอบ Android O ผ่าน Beta Program แล้ว

        สำหรับเครื่องต่างๆที่รองรับการอัพเดท Android O จะสามารถเข้าร่วม Beta Program เพื่อรับอัปเดตเป็น Android O ผ่าน OTA ได้แล้ว โดยเข้าไปดูรายละเอียดได้ที่ https://android.com/beta

Android Support Library v26 

        ในที่สุดก็มาถึงเวอร์ชัน 26 แล้ว ซึ่งในเวอร์ชันจะตัดเวอร์ชันเก่าออกไปทำให้รองรับ Min SDK เขยิบขึ้นมาเป็น 14 แล้ว ซึ่งการตัดเวอร์ชันเก่าๆออกจะช่วยลดขนาด Library อีกทั้งยังทำให้ Method Count ลดลงไปถึงมากกว่า 1,400 Method เลยทีเดียว และล่าสุดก็ได้ย้ายไปอยู่บน Maven Repository ของ Google เรียบร้อยแล้ว

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

Font Resource Supporting

        Font Resource นั้นเป็นฟีเจอร์ที่ถูกเพิ่มเข้ามาใหม่ใน Android O ซึ่งจะช่วยแก้ปัญหาเวลานักพัฒนาต้องการทำแอปฯที่ใช้ Custom Font เพราะแบบเดิมที่ทำกันอยู่คือต้องสร้าง Custom View แล้วกำหนดฟอนต์ผ่านโค้ดมาตลอด ซึ่งน่าแปลกใจตรงที่มี Support Font Resource ให้ด้วยนี่ล่ะ

        ซึ่งความสามารถทั้งหมดนั้นเหมือนกับ Font Resource ที่อยู่บน Android O เลย นั่นหมายความว่าผู้ที่หลงเข้ามาอ่านสามารถเปลี่ยนมาใช้ Font Resource แทนวิธีเก่าๆได้แล้ว

        สามารถดูรายละเอียดเพิ่มเติมได้ที่ Fonts in XML

Downloadable Fonts Supporting

        นอกจาก Font Resource แล้ว Android Support Library เวอร์ชัน 26 นี้ ยังรองรับ Downloadable Font ที่เพิ่มเข้ามาใน Android O ด้วย นั่นหมายความว่าผู้ที่หลงเข้ามาอ่านสามารถทำแอปฯที่ผู้ใช้สามารถเปลี่ยนฟอนต์ภายในแอปฯได้ โดยที่ฟอนต์นั้นจะถูกดาวน์โหลดมาทีหลัง ไม่ได้มีอยู่ในเครื่องตั้งแต่แรก

        ขอย้อนไปที่ Downloadable บน Android O ก่อนนะ ซึ่ง Android O นั้นจะมี Font Provider เพิ่มเข้ามาให้ เพื่อช่วยจัดการเรื่องการดาวน์โหลดฟอนต์มาลงในเครื่องให้  (รวมไปถึงขั้นตอนที่ผู้ใช้เลือกฟอนต์ที่จะติดตั้งลงในเครื่อง)


        แล้วเวอร์ชันเก่าๆที่ต่ำกว่า Android O จะทำยังไงล่ะ? เพราะว่ามันไม่มี Font Provider ตั้งแต่แรก

        คำตอบก็คือ Google Play Services v11 จะมี Font Provider มาให้ในตัวครับ

        ดังนั้นเมื่อแอปฯทำงานอยู่บนเวอร์ชันที่ต่ำกว่า Android O ก็จะเรียกใช้งาน Font Provider จาก Google Play Services โดยการเรียกใช้งาน Font Provider จะต้องเปลี่ยนไปเรียกคลาสที่ชื่อว่า FontsContractCompat แทน


        แต่ทว่า Google Play Services ก็ยังคงมีปัญหาที่บางเครื่องไม่ได้อัปเดตเป็นเวอร์ชันล่าสุด ซึ่งตัว FontsContractCompat ก็จะช่วยจัดการให้แล้ว โดยจะส่ง Fallback กลับมาให้แทน

        สามารถดูรายละเอียดเพิ่มเติมได้ที่ Downloadable Fonts

Emoji Compatibility Library

        สำหรับบางแอปฯที่มีการใช้งาน Emoji อาจจะเคยเจอปัญหาเรื่อง Emoji บางตัวไม่มีในเวอร์ชันเก่าๆ จึงทำให้แสดงผลไม่ได้ ซึ่งใน Android Support Library ก็จะใช้ความสามารถของ Font Provider ช่วยเรื่องนี้ได้ โดยมีวิธีให้เลือกสองวิธี วิธีแรกคือให้ Font Provider ใน Google Play Services ช่วยจัดการให้ หรือจะเตรียม Emoji ไว้โดยติดตั้งผ่าน Dependency (ขนาด 7MB!!)

dependencies {
    ...
    compile "com.android.support:support-emoji-bundled:$version"
}

        สามารถดูรายละเอียดเพิ่มเติมได้ที่ Emoji Compatibility

TextView Autosizing

        TextView Autosizing ก็เป็นอีกหนึ่งในฟีเจอร์ของ Android O ที่ Backward Compatibility เหมือนกัน ซึ่งความสามารถเหมือนกับบน Android O ทั้งหมดเลย~

<TextView
    ..
    app:autoSizeTextType="uniform" 
    app:autoSizeMinTextSize="12sp"
    app:autoSizeMaxTextSize="100sp"
    app:autoSizeStepGranularity="2sp" />

Dynamic Animation Library

        ไม่ได้อยู่ใน Android Support Library โดยตรง แต่เป็น Library สำหรับ Animation API ตัวใหม่ที่แยกออกมาอีกชุดหนึ่ง โดยจะเน้นไปที่ Physics-based Animation เพราะ Animation API ที่มีอยู่ตอนนี้ยังไม่ค่อยดูเป็นธรรมชาติซักเท่าไร ซึ่งจะขอพูดถึงในหัวข้อถัดไปแทน (เพราะมีรายละเอียดเยอะพอสมควร)

        ทั้งหมดที่น่าสนใจก็มีประมาณนี้แหละนะ

        ว่าแต่... มีนักพัฒนาคนไหนที่ไม่ได้ใช้ Android Support Library อยู่มั้ยนะ?

Dynamic Animation : Animation Library ตัวใหม่

        ทุกวันนี้เวลาจะทำลูกเล่นอย่าง Animation ในแอปฯก็คงจะไม่พ้น Animator API อย่าง AnimatorSet และ ObjectAnimator เนอะ แต่ปัญหาที่ส่วนใหญ่เจอกันก็คือมันเป็นการเคลื่อนไหวที่ค่อนข้างจะทื่อๆ ไม่รู้สึกถึงความเป็นธรรมชาติ ถ้าอยากจะทำให้ดูเป็นธรรมชาติก็ต้องเขียนโค้ดหลายร้อยบรรทัด แถมไม่ใช่ง่ายๆด้วย


        ดังนั้นทีมแอนดรอยด์จึงพัฒนา Library ตัวนี้ขึ้นมาโดยใช้หลักทางเคลื่อนที่แบบฟิสิกส์ (Physics-based Animation) เพื่อให้การเคลื่อนไหวดูสมจริงมากขึ้น มีแรงทางธรรมชาติเข้ามาเกี่ยวข้อง

        โดย Animator API จะมีปัญหาอย่างหนึ่งที่ขัดใจเจ้าของบล็อกมากๆก็คือความเร็วในการเคลื่อนที่ของวัตถุนั้นขึ้นอยู่กับระยะเวลา (Duration) ดังนั้นความเร็วในการเคลื่อนที่จะสัมพันธ์กับเวลาและระยะทาง จึงทำให้ไม่สามารถกำหนดความเร็วในการเคลื่อนที่ได้

        แต่สำหรับ Dynamic Animation จะใช้ความเร็วในการเคลื่อนที่ (Velocity) เป็นตัวแปรหลักในการทำงาน จึงทำให้ระยะเวลาที่ใช้ใน Animation ขึ้นอยู่กับค่าความเร็วและระยะทางแทน

dependencies {
    ...
    compile "com.android.support:support-dynamic-animation:26.0.0-beta1"
}

        Library ดังกล่าวนั้นรองรับเวอร์ชันต่ำสุดที่ API 16 โดยตอนนี้มี Animation ให้ใช้งานอยู่ 2 คลาสด้วยกัน คือ

        • FlingAnimation
        • SpringAnimation

Spring Animation

        Spring Animation เป็นการเคลื่อนที่แบบสปริง (Spring-based Animation) ซึ่งใช้ Concept ในการเคลื่อนที่ด้วยแรงสปริง ดังนั้นอัตราเร็วและแรงเสียดทานในการเคลื่อนที่ของวัตถุจะใช้รูปแบบการเคลื่อนที่ของสปริง


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

SpingAnimation springAnimation = new SpringAnimation(view, DynamicAnimation.TRANSLATION_Y, 0)
        .setStartValue(200)
        .setStartVelocity(3000)
        .start();

        จะเห็นว่ารูปแบบคำสั่งคล้ายกับ ObjectAnimator ที่สามารถกำหนดค่าต่างๆและกำหนด View ที่ต้องการให้เล่น Animation ได้เลย (แต่จากที่ส่องโค้ดข้างใน พบว่าไม่ได้สร้างขึ้นมาจาก ObjectAnimator แต่อย่างใด แค่ทำให้ใช้งานคล้ายๆกันเฉยๆ)

        ในการกำหนดรูปแบบการเคลื่อนไหวของสปริงจะมีคลาสที่ชื่อว่า SpringForce ที่สามารถกำหนดค่า Damping และ Stiffenss ได้ ซึ่งจะทำให้รูปแบบการเคลื่อนที่แตกต่างออกไปตามต้องการ

SpringForce springForce = new SpringForce(0)
        .setDampingRatio(0.4f)
        .setStiffness(500f);
SpingAnimation springAnimation = new SpringAnimation(view, DynamicAnimation.TRANSLATION_Y, 0)
        .setStartValue(200)
        .setStartVelocity(3000)
        .setSpring(springForce)
        .start();

        ถ้าไม่อยากกำหนดเป็นตัวเลขให้วุ่นวาย ก็มีตัวแปรคงที่ให้เรียกใช้งานได้ทันที

// Stiffness Constant Value
SpringForce.STIFFNESS_VERY_LOW
SpringForce.STIFFNESS_LOW
SpringForce.STIFFNESS_MEDIUM
SpringForce.STIFFNESS_HIGH

// Damping Ratio Constant Value
SpringForce.DAMPING_RATIO_NO_BOUNCY
SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
SpringForce.DAMPING_RATIO_LOW_BOUNCY
SpringForce.DAMPING_RATIO_HIGH_BOUNCY


Fling Animation

        Fling Animation เป็นการเคลื่อนที่แบบมีแรงเสียดทานเข้ามาเกี่ยวข้อง (Fling-based Animation) ซึ่งใช้ Concept เป็นการเคลื่อนที่ของวัตถุที่อยู่บนพื้นผิวที่มีแรงเสียดทาน


        จากภาพจะเป็นการปัดนิ้วบน View ที่ Scroll ได้ เมื่อออกแรงปัดมาก ก็จะทำให้การ Scroll ไปได้ไกล และจะช้าลงเรื่อยๆจนกระทั่งหยุดนิ่ง

FlingAnimation flingAnimation = new FlingAnimation(view, DynamicAnimation.TRANSLATION_Y, 0)
        .setStartValue(200)
        .setStartVelocity(3000)
        .start();

        สำหรับ FlingAnimation จะเหมือนกับ SpringAnimation เลย แต่จะไม่มีคลาส SpringForce เข้ามาเกี่ยวข้อง ดังนั้นจึงขอจบเรื่อง FlingAnimation ไปอย่างรวดเร็วเลยนะ

        ส่วน Event Listener ของ DynamicAnimation ก็จะมีลักษณะแบบนี้

FlingAnimation flingAnimation = ...

flingAnimation.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {
    @Override
    public void onAnimationUpdate(DynamicAnimation animation, float value, float velocity) {

    }
});

flingAnimation.addEndListener(new DynamicAnimation.OnAnimationEndListener() {
    @Override
    public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, float velocity) {

    }
});

        และที่น่าสนใจสำหรับ DynamicAnimation อีกอย่างหนึ่งก็คือมี Scroll Animation สำหรับใช้งานกับ ScrollView, ListView หรือ RecyclerView ได้ด้วย

DynamicAnimation.ALPHA
DynamicAnimation.ROTATION
DynamicAnimation.ROTATION_X
DynamicAnimation.ROTATION_Y
DynamicAnimation.SCALE_X
DynamicAnimation.SCALE_Y
DynamicAnimation.SCROLL_X
DynamicAnimation.SCROLL_Y
DynamicAnimation.TRANSLATION_X
DynamicAnimation.TRANSLATION_Y
DynamicAnimation.TRANSLATION_Z

        นอกจากนี้ SpringAnimation และ FlingAnimation สามารถเอาไปใช้งานแบบ ValueAnimator ได้เหมือนกันนะเออ

        จากความสามารถโดยรวมของ DynamicAnimation ถือว่าเป็นอีกเรื่องที่เจ้าของบล็อกสนใจมากๆ เพราะเป็น Library ที่ทำให้นักพัฒนาสามารถสร้าง Animation ที่ดูเป็นธรรมชาติและสวยงามมากขึ้น เพราะ Animation ภายในแอปฯจะช่วยดึงดูดความสนใจผู้ใช้ได้เป็นอย่างดี รวมไปถึงทำให้การใช้งานแอปฯดูลื่นไหลสวยงาม

        ไว้จะหาเวลาเขียนเรื่องแบบนี้ละเอียดๆให้อ่านกันทีหลังครับ

ไม่ต้องดาวน์โหลด Plugin และ Library ผ่าน Android SDK Manager แล้ว

        เดิมทีนั้น Plugin และ Library ของทีมแอนดรอยด์แทบทั้งหมดจะอยู่ใน Android SDK Manager ทำให้เวลาอัปเดตเวอร์ชันใหม่ๆก็จะต้องเปิด Android SDK Manager แล้วกดดาวน์โหลด ซึ่งเจ้าของบล็อกก็รู้สึกว่ามันเป็นอะไรที่น่าเบื่อเช่นกัน


        ล่าสุด Plugin และ Library เหล่านี้ได้ถูกย้ายไปไว้บน Maven Repository ของ Google แล้ว จึงทำให้ไม่ต้องมานั่งกดอัปเดตเองอีกต่อไป เพียงแค่ระบุ URL ของ Maven Repository ของ Google ไว้ใน build.gradle ด้วย (Android Studio 3.0 ใส่ไว้เป็น Default แล้ว)

...
buildscript {
    repositories {
        ...
        maven { url 'https://maven.google.com' }
    }
    ...
}

allprojects {
    repositories {
        ...
        maven { url 'https://maven.google.com' }
    }
}
...


        Plugin และ Library ที่ถูกย้ายไป Maven Repository ก็จะมีดังนี้

        • Gradle Plugin
        • Support Libraries
        • Data-Binding Library
        • Constrain Layout Library
        • Instant Apps Library
        • Architecture Components
        • Android Testing Libraries
        • Espresso

        น่าจะทำแบบนี้ได้ตั้งนานแล้วนะ...

Firebase


        ขอยกเนื้อหาสำหรับการเปลี่ยนแปลงและความสามารถใหม่ๆของ Firebase ให้กับบทความ มีอะไรใหม่จากทีม Firebase ที่งาน Google I/O 2017 แทนนะครับ

Instant Apps

        Instant App เป็นฟีเจอร์ที่ทาง Google ได้เปิดตัวขึ้นในปีที่แล้ว ซึ่งจะทำให้ผู้ใช้สามารถใช้งานแอปฯได้ทันทีโดยไม่ต้องติดตั้งลงในเครื่อง โดยจะอยู่ในรูปแบบของ URL ที่กดแล้วจะเปิดหน้าแอปฯตัวนั้นขึ้นมาได้ทันที แต่ว่าไม่ใช่การเปิดหน้าเว็ปหน้า เป็นการเปิดแอปฯที่ทำงานแบบ Native โดยสมบูรณ์


        ในการเปิดใช้งานครั้งแรก ตัวเครื่องจะทำการดาวน์โหลดข้อมูลของแอพมาเก็บไว้ในเครื่องให้โดยอัตโนมัติ จากนั้นก็จะทำงานได้เหมือนกับแอปฯทั่วๆไป (แต่ไม่ต้องผ่านขั้นตอนการติดตั้ง) โดยมี Google Play เป็นหัวใจสำคัญในการทำงาน (เดาว่าทำเป็น Container)
        ซึ่งการสร้าง Instant App นั้นจะทำผ่าน Android Studio ได้เลย โดยสร้างแอปฯแยกออกมาจากตัวเดิม โค้ดที่ใช้จะเป็นการหยิบโค้ดที่มีอยู่แล้วมาทำเป็น Instant App แทน ซึ่งจะต้องใช้วิธีที่เรียกว่า Modularization เข้ามาช่วย หรือพูดง่ายๆก็คือการแยกโค้ดตามฟีเจอร์ออกมาเป็นแต่ละ Module แทนที่จะรวมอยู่ใน Module เดียวกัน และ APK สำหรับ Instant App จะต้องมีขนาดไม่เกิน 4MB


        จากรูปแบบดังกล่าวจะเห็นว่าโค้ดข้างในก็ยังคงเป็นเหมือนเดิม เพิ่มเติมคือเปลี่ยนรูปแบบของโครงสร้างโค้ด ทำให้ได้โค้ดที่สามารถ Build เป็น Instant App หรือ Installable App (แบบปกติ) ก็ได้

        ซึ่งการทำ Modularization ที่ว่าก็คือการย้ายโค้ดออกไปไว้ใน Module น่ะแหละ โดย Android Studio จะย้ายโค้ดไปไว้ที่อีก Module ให้ โดยมีการตรวจสอบว่ามีไฟล์ไหนที่เกี่ยวข้องกับไฟล์นั้นๆหรือไม่ จะได้ย้ายไปทั้งยวงเลย ดังนั้นหัวใจสำคัญของการทำ Instant App คือต้องวางโครงสร้างโค้ดที่ยืดหยุ่นและสามารถทำงานแยกจากกันได้อย่างอิสระ ถ้าทีมพัฒนาทีมไหนไม่ได้วางไว้ตั้งแต่แรกก็อาจจะต้องไล่ปรับใหม่ทั้งยวงเลยก็ว่าได้ (หรือไม่ทำ Instant App ไปเลย ฮาๆ)

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

        อ่านรายละเอียดเพิ่มเติมของ Instant Apps ได้ที่ g.co/InstantApps

ดูรายละเอียดของอุปกรณ์แอนดรอยด์ผ่าน Device Catalogue บน Google Play Developer Console

        จริงๆแล้วบน Google Play Developer Console นั้นมีอัปเดตอะไรหลายๆอย่างมาก ซึ่งที่เจ้าของบล็อกชอบมากที่สุดก็คือ Device Catalogue ที่จะแสดงรายละเอียดของอุปกรณ์แอนดรอยด์แต่ละรุ่นที่เป็นประโยชน์ต่อนักพัฒนา

        โดยเข้าไปดูได้ที่ https://play.google.com/apps/publish/ (ต้องเคยสมัครเป็น Developer ก่อน) > กดเข้าไปดูแอปฯที่มีอยู่ > Release Management > Device catalogue


        สามารถกดดูทุกเครื่องที่มีในฐานข้อมูลของ Google Play Store ได้ โดยอุปกรณ์แต่ละเครื่องจะมีบอกข้อมูลสำคัญต่างๆไม่ว่าจะเป็น CPU, RAM, GPU, Screen Size, Screen Density และ API Version


สรุปแนวโน้มของ Android Development

        เรื่องแอนดรอยด์ที่พีคที่สุดของปีนี้ก็คงไม่พ้นเรื่อง Kotlin ซึ่งเป็นเสมือนสัญญาณบอกว่าถึงเวลาแล้วที่จะต้องเรียนรู้ Kotlin ติดตัวไว้ด้วย ในด้านการเรียนการศึกษาอาจจะยังไม่สำคัญมากนัก ไม่จำเป็นต้องเปลี่ยนเนื้อหาไปเป็น Kotlin ก็ได้ เพราะสุดท้ายแล้วควรให้ผู้เรียนโฟกัสไปที่ Java เป็นหลักก่อน เมื่อเข้าใจ Java มากพอสมควรแล้ว การจะเรียนรู้ Kotlin เพิ่มเติมก็ไม่ใช่เรื่องยากนัก

        แต่สำหรับสายงาน Android Developer นั้นถือว่าจำเป็นต้องรู้แล้วครับ อาจจะไม่จำเป็นต้องรู้แบบทะลุปรุโปร่ง แต่อย่างน้อยก็ต้องอ่านโค้ด Kotlin ให้เป็นครับ เพราะในอีกไม่นานก็จะได้เห็นแอปฯต่างๆพากันใช้ Kotlin กันแน่นอน (เพราะที่ผ่านมายังลังเลกันอยู่) ไม่ควรยึดติดกับ Java อีกต่อไปแล้ว ในเมื่อมี Kotlin เป็นทางเลือก ก็ควรเรียนรู้เพิ่มเติมเพื่อเพิ่มโอกาสให้กับ Career Path ให้มากขึ้นครับ


        เรื่องที่พีครองลงมาก็คงจะเป็น Architecture Components นี่แหละ เพราะว่าเรื่อง Code Structure บนแอนดรอยด์นั้นเป็นอะไรที่ถกเถียงกันมานานแล้ว โดยเฉพาะการนำ Pattern ใหม่ๆอย่าง MVVM หรือ MVP ไปใช้งานกับแอนดรอยด์ที่ถูกวิธี (หลายๆคนก็คงมองเห็นถึงปัญหานี้กันอยู่แล้ว) และนั่นก็ทำให้ทีมแอนดรอยด์ได้ออกแบบ Pattern ที่ปลอดภัย มีประสิทธิภาพ ยืดหยุ่น และเขียนเทสได้ (อันนี้สำคัญ) โดยที่นักพัฒนาไม่จำเป็นต้องคิดเอง ลองเอง เจ็บเอง สามารถทำความเข้าใจและหยิบไปใช้งานได้เลย

        แต่ก็อย่างที่บอกไปในตอนแรกครับว่าถ้าคิดว่า Pattern ที่ใช้กันอยู่ภายในทีมนั้นโอเคแล้ว ไม่มีปัญหาอะไร ก็ใช้ต่อไปได้ครับ แต่ถ้ากำลังมองหาวิธีหรือ Pattern ใหม่ๆที่จะมาแก้ปัญหาในที่มีอยู่ในโปรเจค ขอแนะนำ Architecture Components เลยครับ


        ในปีนี้ Sundar Pichai ได้กล่าวไว้ชัดเจนว่า "Mobile first to AI first" ซึ่งก็คือ Google จะเน้นโฟกัสไปที่ AI มากขึ้นอย่างเห็นได้ชัด แต่นั่นก็ไม่ได้หมายความว่า Mobile จะกลายเป็นสิ่งที่ล้าสมัยหรือตายลง (ใครที่กังวล Career Path ด้านนี้ก็อย่าคิดมากไป) เพราะสุดท้ายแล้วการที่ AI พัฒนาตัวเองได้นั้นจะต้องมีสิ่งที่เรียกว่า "ข้อมูล" ในปริมาณที่เยอะมาก และการจะหาข้อมูลเหล่านั้นมาใช้พัฒนา AI ได้ ส่วนหนึ่งก็มาจาก Mobile ที่ใช้กันอยู่ทุกวันนี้นั่นเอง ดังนั้นต่อไปก็คงจะเข้าสู่ AI แน่นอนครับ แต่ในนั้นก็จะมี Mobile เป็นหัวใจสำคัญเช่นกัน โอเคเนอะ

        ปีนี้มีอะไรใหม่เยอะแยะไปหมด เยอะมากจนทำเอานักพัฒนาแอนดรอยด์ตามกันแทบไม่ทัน แต่ถึงยังไงก็ควรหมั่นอัปเดตอยู่เสมอครับ เพราะสิ่งใหม่ๆเหล่านี้นี่แหละที่จะทำให้แอปฯของผู้ที่หลงเข้ามาอ่านมีประสิทธิภาพที่ดีขึ้น มีลูกเล่นที่ใหม่ขึ้น รวมไปถึงความสามารถของผู้ที่หลงเข้ามาอ่านที่จะเพิ่มขึ้นมากด้วยนั่นเอง ดังนั้นนอกจากจะเรียนรู้ความสามารถใหม่ๆแล้วก็อย่าลืมหยิบไปใช้งานกันด้วยนะ

        แล้วเจอกันอีกทีตอน Google I/O 2018 ครับ