07/09/2018, 18:13

CÁC ỨNG DỤNG REATIVE VỚI MODEL-VIEW-INTENT - PHẦN 1 - MODEL

Khi tôi hình dung ra tôi đã mô hình hóa Model classes của tôi lần nào cũng sai, tồn tại rất nhiều vấn đề và nhức đầu, trước đây tôi đã từng có một vài chủ đề về nền tảng Android. Cuối cùng tôi đã có thể xây dựng ứng dụng Reactive bằng cách sử dụng RxJava và Model-View-Intent (MVI) như tôi chưa ...

Khi tôi hình dung ra tôi đã mô hình hóa Model classes của tôi lần nào cũng sai, tồn tại rất nhiều vấn đề và nhức đầu, trước đây tôi đã từng có một vài chủ đề về nền tảng Android. Cuối cùng tôi đã có thể xây dựng ứng dụng Reactive bằng cách sử dụng RxJava và Model-View-Intent (MVI) như tôi chưa từng có được trước đó, thông qua các ứng dụng tôi đã xây dựng cho đến nay Reactive cũng không cùng mức độ như tôi sẽ mô tả trong loạt bài đăng blog này. Trong phần đầu tiên, tôi muốn nói về Model và tại sao Model lại quan trọng. Những gì tôi làm có ý nghĩa gì khi "các Model được mô hình hoá một cách sai ?" Tốt thôi, có rất nhiều kiểu kiến trúc mà ở đó tách View ra khỏi Model của bạn. Những tính năng phổ biến nhất, ít nhất là trong Android development, là Model-View-Controller (MVC), Model-View-Presenter (MVP) và Model-View-ViewModel (MVVM). Bạn có nhận thấy điều gì đó bằng cách nhìn vào tên của những mô hình này ? Tất cả chúng đều nói về "Model". Tôi nhận ra rằng không có một Model cho tất cả. Ví dụ: Chỉ cần tải danh sách persons từ một backend. Một mô hình MVP " traditional " có thể như sau:

class PersonsPresenter extends Presenter<PersonsView> {

  public void load(){
    getView().showLoading(true); // Displays a ProgressBar on the screen

    backend.loadPersons(new Callback(){
      public void onSuccess(List<Person> persons){
        getView().showPersons(persons); // Displays a list of Persons on the screen
      }

      public void onError(Throwable error){
        getView().showError(error); // Displays a error message on the screen
      }
    });
  }
}

Nhưng Model ở đâu hay Model là gì ? là backend phải không ? Không, đó là business logic. Có phải là kết quả là một danh sách ? Không, đó chỉ là một thứ mà View của chúng ta hiển thị giữa nhiều cái khác, nó giống như một loading indicator hoặc một error message. Vậy thì Model thực sự là cái gì? Theo quan điểm của tôi, nên có một class Model như thế này:

class PersonsModel {
  // In a real application fields would be private
  // and we would have getters to access them
  final boolean loading;
  final List<Person> persons;
  final Throwable error;

  public(boolean loading, List<Person> persons, Throwable error){
    this.loading = loading;
    this.persons = persons;
    this.error = error;
  }
}

Và sau đó Presenter có thể được thực hiện như thế này:

class PersonsPresenter extends Presenter<PersonsView> {

  public void load(){
    getView().render( new PersonsModel(true, null, null) ); // Displays a ProgressBar

    backend.loadPersons(new Callback(){
      public void onSuccess(List<Person> persons){
        getView().render( new PersonsModel(false, persons, null) ); // Displays a list of Persons
      }

      public void onError(Throwable error){
          getView().render( new PersonsModel(false, null, error) ); // Displays a error message
      }
    });
  }
}

Bây giờ View có một Model mà sau đó sẽ được "rendered" trên màn hình. Khái niệm này không có gì mới mẻ. Định nghĩa MVC ban đầu của Trygve Reenskaug từ năm 1979 có một khái niệm tương tự: Quan sát thấy sự thay đổi của Model trên View. Thật không may, cụm từ MVC đã được sử dụng để mô tả quá nhiều mẫu khác nhau mà không thực sự giống như Reenskaug đã xây dựng vào năm 1979. Ví dụ: backend developers sử dụng MVC frameworks, iOS có ViewController và MVC trên Android có nghĩa là gì? Activities là Controller? Vậy ClickListener là gì? Ngày nay thuật ngữ MVC là một sai lầm lớn, lạm dụng và hiểu sai về những gì mà Reenskaug đã xây dựng lúc đầu. Nhưng chúng ta nên dừng cuộc thảo luận về MVC ở đây, điều này có thể ngoài tầm kiểm soát. Hãy trở lại với những gì tôi đã đưa ra ngay từ đầu. Có một Model giải quyết rất nhiều vấn đề mà chúng ta thường gặp phải khi phát triển Android:

  1. Vấn đề State
  2. Xoay màn hình
  3. Navigation on the back stask
  4. Process death
  5. Tính bất biến(Immutability) và unidirection data flow
  6. Debuggable và reproducible states
  7. Khả năng kiểm tra (Testability)

Hãy thảo luận những điểm này và chúng ta hãy xem cách triển khai "truyền thống" của MVP và MVVM đối phó với những vấn đề này như thế nào và cuối cùng là "Model" có thể giúp ngăn ngừa những sai lầm phổ biến.

1. The State Problem

Reactive Apps - đây là một buzzword, phải không? Ý tôi là những ứng dụng có một UI phản ánh sự thay đổi các trạng thái. Ah, ở đây chúng ta có một từ khác khá hay: "State ". "State " là gì ? Vâng, hầu hết "State " được hiểu là những gì chúng ta thấy trên màn hình, như "loading state" khi có View hiển thị ProgressBar. Vấn đề mấu chốt nằm ở đó : chúng tôi hướng tới các nhà phát triển có xu hướng tập trung vào giao diện người dùng. Đó không phải là điều xấu vì cuối cùng một giao diện người dùng quyết định xem người dùng có sử dụng ứng dụng của chúng tôi hay không và do đó ứng dụng thành công như thế nào. Nhưng hãy xem ví dụ mã nguồn MVP cơ bản ở trên (không phải bằng cách sử dụng PersonsModel). Ở đây trạng thái của giao diện người dùng được phối hợp bằng Presenter, từ Presenter nói với View cần hiển thị những gì. Điều này cũng đúng đối với MVVM. Trong bài đăng blog này tôi muốn phân biệt giữa hai cách thực hiện MVVM: Phiên bản đầu tiên với Android's data binding và tùy chọn thứ hai sử dụng RxJava. Trong MVVM dữ liệu được đẩy thẳng đến ViewModel:

class PersonsViewModel {
  ObservableBoolean loading;
  // ... Other fields left out for better readability

  public void load(){

    loading.set(true);

    backend.loadPersons(new Callback(){
      public void onSuccess(List<Person> persons){
      loading.set(false);
      // ... other stuff like set list of persons
      }

      public void onError(Throwable error){
        loading.set(false);
        // ... other stuff like set error message
      }
    });
  }
}

In MVVM với RxJava chúng tôi không sử dụng data binding engine nhưng bind Observable đến UI Widgets trong View, cho ví dụ:

class RxPersonsViewModel {
  private PublishSubject<Boolean> loading;
  private PublishSubject<List<Person> persons;
  private PublishSubject loadPersonsCommand;

  public RxPersonsViewModel(){
    loadPersonsCommand.flatMap(ignored -> backend.loadPersons())
      .doOnSubscribe(ignored -> loading.onNext(true))
      .doOnTerminate(ignored -> loading.onNext(false))
      .subscribe(persons)
      // Could also be implemented entirely different
  }

  // Subscribed to in View (i.e. Activity / Fragment)
  public Observable<Boolean> loading(){
    return loading;
  }

  // Subscribed to in View (i.e. Activity / Fragment)
  public Observable<List<Person>> persons(){
    return persons;
  }

  // Whenever this action is triggered (calling onNext() ) we load persons
  public PublishSubject loadPersonsCommand(){
    return loadPersonsCommand;
  }
}

*cảm ơn các bạn đã quan tâm, bài viết sẽ được update tiếp phần còn lại trong thời gian sớm nhất * <<<<<<<<<<<<

0