07 August 2016

มารู้จักกับ RxJava และ RxAndroid กันเถอะ [ตอนที่ 1]

Updated on


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

ReactiveX คืออะไร?

        ทีมพัฒนาได้อธิบายตัว ReactiveX (ต่อไปขอเรียกสั้นๆว่า Rx) ไว้ว่าเป็น "An API for asynchronous programming with observable streams" หรือก็คือชุดพัฒนาหรือ Library ที่จะช่วยให้เขียนโค้ดทำงานแบบ Asynchronous ให้มีประสิทธิภาพ โดยใช้รูปแบบในการรับส่งข้อมูลด้วย Observer Pattern

        ซึ่ง Rx เป็นอีกหนึ่งทางเลือกที่น่าสนใจในการเขียนโค้ดที่จะมาทำงานแทนที่วิธีการเขียนโค้ด Asycnhronous แบบเดิมๆ โดยตัวมันมีการทำงานที่ยืดหยุ่นและมีความสามารถหลากหลาย อีกทั้งยังรองรับภาษาต่างๆได้เกือบครอบคลุม ไม่ว่าจะเป็น Java, JavaScript, Scala, Swift และอื่นๆอีกมากมาย โดยจะแยก Library เป็นชื่อต่างๆ อย่างของ Java จะใช้ชื่อว่า RxJava และยังมีแยกออกมาเป็น RxAndroid อีกด้วย

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

การทำงานของ Rx

        Rx ใช้ Observer Pattern ที่จะมีส่วนประกอบหลักอยู่สองส่วนด้วยกันคือ Observable และ Subscriber ทั้งสองนี้จะทำงานอยู่คู่กันตลอดเวลา


        Observable เป็นตัวที่จะคอยส่งข้อมูล (Data) ไปให้ Subsctiber

        Subscriber เป็นตัวที่จะรอรับ Data ที่ Observable ส่งมาให้

        Data จะมาจากไหนก็ได้ตามที่นักพัฒนากำหนดคำสั่งไว้ให้ มันไม่สนใจครับ ว่าคำสั่งทำงานยังไง มันรู้แค่ว่ารอผลลัพธ์ (Data) ที่ได้แล้ว Observable จะส่ง Data ตัวนั้นไปให้ Subscriber

        ดูแล้วก็คล้ายๆ Callback ธรรมดาๆเนอะ

        และ Observable สามารถส่ง Data ให้ Subscriber แบบต่อเนื่องได้ ไม่จำเป็นต้องส่งครั้งเดียวแล้วจบไป


        ซึ่งนั่นก็ดูคล้ายๆกับ Listener อยู่ดีน่ะแหละ

แล้วถ้าแบบนี้ล่ะ?

        สมมติว่าเจ้าของบล็อกต้องการดึงข้อมูลจาก Service 2 ที่ ซึ่ง Data ที่ได้ก็จะเป็นคนละชุด (ในภาพก็เลยกำหนดเป็น Data1 และ Data2) โดย Service ทั้ง 2 ที่นี่ใช้เวลาในการทำงานไม่เท่ากัน


        ซึ่ง Requirement อย่างหนึ่งที่เจ้าของบล็อกเจออยู่บ่อยๆคือ

        "อยากให้ Service ทั้ง 2 ทำงานจนเสร็จทั้งคู่ก่อน แล้วค่อยทำคำสั่งต่อไป"

        นั่นล่ะครับ ที่ Rx สามารถตอบโจทย์ได้โดยที่เจ้าของบล็อกไม่ต้องไปนั่งเขียนโค้ดเพื่อเช็คว่า Service ทั้งคู่ทำงานเสร็จแล้วหรือยัง เพราะ Rx มีความสามารถหนึ่งที่เรียกว่า Zip

        Zip จะรอจนกว่าจะได้ Data1 และ Data2 แล้วจะส่งไปให้ Subscriber พร้อมๆกันทั้ง 2 ตัว

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

        ซึ่ง Zip เป็นหนึ่งในความสามารถที่ทาง Rx เรียกว่า Operator และที่มีอยู่ทั้งหมดใน Rx บอกเลยว่า "โคตรเยอะ" เจ้า Zip นี่เป็นแค่เพียงเสี้ยวนึงเท่านั้น..

ยกตัวอย่างอีกหนึ่งตัวละกัน

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

        แต่จะดีกว่ามั้ยถ้าแยกมันออกมาให้เป็นขั้นเป็นตอนที่ดูดีกว่าการยัดโค้ดทั้งหมดไว้ตอนปลายทาง


        Rx มี Operator ตัวหนึ่งที่ชื่อ Map ที่จะช่วยให้นักพัฒนาสามารถยัดโค้ดไว้ระหว่างทางได้ ซึ่งโค้ดที่ว่าเป็นโค้ดอะไรก็ได้ที่ไปกระทำกับ Data ที่ได้มาในตอนแรก ให้กลายเป็น Data ตัวใหม่ที่ต้องการจริงๆ และจะใช้ Map มากกว่าหนึ่งครั้งก็ได้นะ


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

ลองเปรียบเทียบโค้ดธรรมดาๆกับโค้ด Rx 

แบบ Synchronous 

        ต้องการดึงค่ารายชื่อสถานที่มาแสดงใน Recycler View โดยที่จะดึงจากคำสั่ง getLocationList() จากนั้นค่าที่ได้ก็จะถูกนำไปกำหนดไว้ใน Adapter ของ Recycler View

LocationRecyclerAdapter adapter = ...

...

List<Location> locations = getLocationList();
adapter.setAdapter(locations);

...

public List<Location> getLocationList() {
    List<Location> locationList = ...
    return locationList;
}

        เมื่อเขียนแบบ Rx

LocationRecyclerAdapter adapter = ...

...

Observable.just(getLocationList())
        .subscribe(new Observer<List<Location>>() {
            @Override
            public void onCompleted() { }

            @Override
            public void onError(Throwable e) { }

            @Override
            public void onNext(List<Location> locations) {
                adapter.setLocationList(locations);
            }
        });

...

public List<Location> getLocationList() {
    List<Location> locationList = ...
    return locationList;
}

แบบ Asynchronous

        เจ้าของบล็อกอยากจะเขียนแอปฯที่ไปดึงข้อมูลจาก API ซักตัวมาแสดงผล โค้ดในส่วนของการติดต่อกับ Server ก็จะได้โค้ดออกมาประมาณนี้

TextView tvUserAddress = ...

...

AwesomeApiService.getUserInfoList(new OnUserInfoCallback() {
    @Override
    public void onResultSuccess(UserInfoResult userInfoResult) {
        tvUserAddress.setText(userInfoResult.getName());
    }

    @Override
    public void onResultError(Exception e) {
        tvUserAddress.setText("-");
    }
});

        สมมติว่าคำสั่ง AwesomeApiService.getUserInfoList เป็นคำสั่งที่จะดึงข้อมูลจาก Server แล้วส่งผลลัพธ์กลับมาเป็น onSuccess หรือ onError

        เมื่อใช้ Rx แทนก็จะกลายเป็นแบบนี้

Observable.fromCallable(new Callable<UserInfoResult>() {
            @Override
            public UserInfoResult call() throws Exception {
                return AwesomeApiService.getUserInfoList();
            }
        })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Observer<UserInfoResult>() {
            @Override
            public void onCompleted() { }

            @Override
            public void onError(Throwable e) {
                tvUserAddress.setText("-");
            }

            @Override
            public void onNext(UserInfoResult userInfoResult) {
                tvUserAddress.setText(userInfoResult.getName());
            }
        });

        ตอนนี้ผู้ที่หลงเข้ามาอ่านอาจจะมีความสงสัยมากมายเกี่ยวกับโค้ดของ Rx เพราะดูเผินๆแล้วมันดูยุ่งยากและวุ่นวายกว่าวิธีแบบเดิมๆจึง แล้ว Rx จะมาเปลี่ยนชีวิตของนักพัฒนาให้ดีขึ้นได้ยังไงล่ะ?

โค้ดทำไมมันดูหยุบหยับจังเลย

        เมื่อลองดูที่โค้ด ตำแหน่งของโค้ดที่เป็น Observable, Subscriber และ Operator จะเป็นแบบนี้


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

        ตอนสร้าง Observable ก็สามารถทำได้หลายรูปแบบ, Operator ที่มีให้ใช้งานก็มีเยอะแยะ ใช้ในรูปแบบการทำงานที่ต่างกัน และ Subscriber ก็กำหนดรูปแบบของ Event ที่ได้จาก Observable ได้เช่นกัน

สรุป

        บอกเลยครับว่าการทำงานของ Rx ทั้งหมดนั้นจะช่วยให้รูปแบบการเขียนโปรแกรมของผู้ที่หลงเข้ามาอ่านนั้นดีขึ้น Flow การทำงานของโค้ดเป็นไปในรูปแบบเดียวกันทั้งหมดตามรูปแบบที่ Rx กำหนดไว้นั่นเอง ถึงแม้ว่าตอนนี้ผู้ที่หลงเข้ามาอ่านอาจจะยังไม่ค่อยเข้าใจรูปแบบการทำงานของมันมากนัก

        ซึ่งในบทความซีรีย์นี้เจ้าของบล็อกก็จะเน้นไปที่การใช้งาน RxJava และ RxAndroid ควบคู่กันนะ ถึงแม้ว่า RxJava จะใช้งานบนแอนดรอยด์ได้อยู่แล้วก็จริง แต่ก็มีความสามารถปลีกย่อยบนแอนดรอยด์ที่ทำให้ต้องใช้ RxAndroid ควบคู่กันไป

        ส่วนวิธีการใช้ RxJava กับ RxAndroid และความสามารถต่างๆของ Rx นั้นจะเป็นยังไงบ้าง และมีอะไรบ้าง เรียกใช้งานยังไง ก็อ่านต่อกันใน [Android Code] มารู้จักกับ RxJava และ RxAndroid กันเถอะ [ตอนที่ 2] ได้เลยครับ