2017-10-13 18 views
10

Gần đây tôi đã quyết định xem xét kỹ hơn Cấu phần Kiến trúc Android mới mà Google đã phát hành, đặc biệt là sử dụng lớp nhận thức vòng đời ViewModel của họ cho kiến ​​trúc MVVM và LiveData.MVVM pattern và startActivity

Miễn là tôi đang xử lý một Hoạt động hoặc một Phân đoạn, mọi thứ đều ổn.

Tuy nhiên, tôi không thể tìm thấy giải pháp tốt để xử lý Chuyển đổi hoạt động. Giả sử, ví dụ: Hoạt động A có nút để khởi chạy Hoạt động B.

Xử lý startActivity() ở đâu?

Theo mẫu MVVM, logic của clickListener phải ở trong ViewModel. Tuy nhiên, chúng tôi muốn tránh tham chiếu đến Hoạt động ở đó. Vì vậy, việc chuyển ngữ cảnh sang ViewModel không phải là một tùy chọn.

Tôi đã thu hẹp một số tùy chọn có vẻ "OK", nhưng không thể tìm thấy bất kỳ câu trả lời thích hợp nào cho "đây là cách thực hiện".

Tùy chọn 1: Có enum trong ViewModel với ánh xạ giá trị để định tuyến có thể (ACTIVITY_B, ACTIVITY_C). Kết hợp với LiveData. Hoạt động sẽ quan sát LiveData này và khi ViewModel quyết định rằng ACTIVITY_C sẽ được khởi chạy, nó sẽ chỉ postValue (ACTIVITY_C). Hoạt động sau đó có thể gọi startActivity() bình thường.

Tùy chọn 2: Mẫu giao diện thông thường. Cùng nguyên tắc như tùy chọn 1, nhưng Activity sẽ thực hiện giao diện. Tôi cảm thấy một chút khớp nối với điều này mặc dù.

Tùy chọn 3: Tùy chọn nhắn tin, chẳng hạn như Otto hoặc tương tự. ViewModel gửi một Broadcast, Activity chọn nó và khởi động những gì nó có. Chỉ có vấn đề với giải pháp này là, theo mặc định, bạn nên đặt thanh ghi/hủy đăng ký của Broadcast đó bên trong ViewModel. Vì vậy, không giúp đỡ.

Tùy chọn 4: Có một lớp định tuyến lớn, ở đâu đó, như đơn hoặc tương tự, có thể được gọi để gửi định tuyến có liên quan đến bất kỳ hoạt động nào. Cuối cùng qua giao diện? Vì vậy, mọi hoạt động (hoặc một BaseActivity) sẽ thực hiện

IRouting { void requestLaunchActivity(ACTIVITY_B); } 

Phương pháp này chỉ làm tôi lo lắng một chút khi ứng dụng của bạn bắt đầu có rất nhiều mảnh vỡ/hoạt động (vì lớp Routing sẽ trở thành khổng lồ)

Vì vậy, đó là nó. Đó là câu hỏi của tôi. Các bạn xử lý như thế nào? Bạn có lựa chọn mà tôi không nghĩ đến không? Bạn cho rằng tùy chọn nào phù hợp nhất và tại sao? Phương pháp tiếp cận được Google đề xuất là gì?

PS: Liên kết điều đó không làm cho tôi bất cứ nơi nào 1-Android ViewModel call Activity methods 2-How to start an activity from a plain non-activity java class?

+1

Cảm ơn. Vui mừng được giúp bạn :-) –

Trả lời

5

NSimon, tuyệt vời của nó mà bạn bắt đầu sử dụng AAC.

Tôi đã viết issue trong aac's-github trước đó.

Có một số cách để thực hiện điều đó.

Một giải pháp sẽ được sử dụng một

WeakReference đến một NavigationController nắm giữ bối cảnh các hoạt động. Đây là một mẫu được sử dụng phổ biến để xử lý nội dung bị ràng buộc theo ngữ cảnh bên trong một ViewModel.

Tôi đánh giá cao điều này vì nhiều lý do. Thứ nhất: điều đó thường có nghĩa là bạn phải giữ một tham chiếu đến NavigationController của bạn để sửa lỗi rò rỉ ngữ cảnh, nhưng không giải quyết được kiến ​​trúc nào cả.

Cách tốt nhất (trong ý kiến ​​của tôi) là sử dụng LiveData là vòng đời nhận thức và có thể thực hiện tất cả nội dung mong muốn.

Ví dụ:

class YourVm : ViewModel() { 

    val uiEventLiveData = SingleLiveData<Pair<YourModel, Int>>() 
    fun onClick(item: YourModel) { 
     uiEventLiveData.value = item to 3 // can be predefined values 
    } 
} 

Sau đó bạn có thể nghe bên trong xem của bạn cho những thay đổi.

class YourFragmentOrActivity { 
    //assign your vm whatever 
    override fun onActivityCreated(savedInstanceState: Bundle?) { 
     var context = this 
     yourVm.uiEventLiveData.observe(this, Observer { 
      when (it?.second) { 
       1 -> { context.startActivity(...) } 
       2 -> { .. } 
      } 

     }) 
    } 
} 

Hãy cẩn thận khi sử dụng MutableLiveData đã sửa đổi, bởi vì sẽ luôn phát ra kết quả mới nhất cho các Quan sát viên mới dẫn đến hành vi xấu. Ví dụ nếu bạn thay đổi hoạt động và quay trở lại nó sẽ kết thúc trong một vòng lặp.

class SingleLiveData<T> : MutableLiveData<T>() { 

    private val mPending = AtomicBoolean(false) 

    @MainThread 
    override fun observe(owner: LifecycleOwner, observer: Observer<T>) { 

     if (hasActiveObservers()) { 
      Log.w(TAG, "Multiple observers registered but only one will be notified of changes.") 
     } 

     // Observe the internal MutableLiveData 
     super.observe(owner, Observer { t -> 
      if (mPending.compareAndSet(true, false)) { 
       observer.onChanged(t) 
      } 
     }) 
    } 

    @MainThread 
    override fun setValue(t: T?) { 
     mPending.set(true) 
     super.setValue(t) 
    } 

    /** 
    * Used for cases where T is Void, to make calls cleaner. 
    */ 
    @MainThread 
    fun call() { 
     value = null 
    } 

    companion object { 
     private val TAG = "SingleLiveData" 
    } 
} 

Tại sao nỗ lực đó lại tốt hơn khi sử dụng WeakReferences, Interfaces hoặc giải pháp nào khác?

Vì sự kiện này phân tách logic UI với logic nghiệp vụ. Nó cũng có thể có nhiều nhà quan sát. Nó quan tâm đến vòng đời. Nó không rò rỉ bất cứ thứ gì.

Bạn cũng có thể giải quyết bằng cách sử dụng RxJava thay vì LiveData bằng cách sử dụng PublishSubject. (addTo yêu cầu RxKotlin)

Cẩn thận vì không làm rò rỉ đăng ký bằng cách phát hành nó trong onStop().

class YourVm : ViewModel() { 
    var subject : PublishSubject<YourItem> = PublishSubject.create(); 
} 

class YourFragmentOrActivityOrWhatever { 
    var composite = CompositeDisposable() 
    onStart() { 
     YourVm.subject 
      .subscribe({ Log.d("...", "Event emitted $it") }, { error("Error occured $it") }) 
       .addTo(compositeDisposable)   
     } 
     onStop() { 
     compositeDisposable.clear() 
     } 
    } 

Cũng chú ý rằng ViewModel bị ràng buộc với Hoạt động HOẶC một đoạn. Bạn không thể chia sẻ ViewModel giữa nhiều Hoạt động vì điều này sẽ phá vỡ "Nhận thức về vòng đời".

Nếu bạn cần dữ liệu đó tồn tại bằng cách sử dụng cơ sở dữ liệu như room hoặc chia sẻ dữ liệu bằng cách sử dụng bưu kiện.

+0

Cảm ơn câu trả lời rất chi tiết. Tôi cũng đang hướng tới cách tiếp cận LiveData, tuy nhiên không nghĩ đến các chỉnh sửa LiveData của bạn. Nhìn chung, tất cả dường như rất "hacky", và nó cảm thấy gần như xấu để làm điều đó theo cách này. Dù sao cũng cảm ơn ! (Chỉnh sửa: chỉ có thể xác thực tiền thưởng trong 20 giờ) – NSimon

+1

Không, nó không phải là hacky. Đó là cách mà người quan sát hoạt động. Bạn nhận được một cú nhấp chuột, đẩy nó vào ViewModel của bạn và nếu có một View (điều này được đảm bảo) thì nó xử lý dữ liệu. Nó thậm chí không phải là một miếng vá :) Đó là cách nó hoạt động. Nhấp vào ist chỉ là một mẫu, nó có thể là mọi "đầu vào dữ liệu". –