manas-chaudhari / android-mvvm

MVVM on Android using RxJava and Data Binding
Apache License 2.0
501 stars 81 forks source link

NonFunctional ViewModels OR ViewModels with lifecycle OR Better Two Way Binding Field #27

Open manas-chaudhari opened 8 years ago

manas-chaudhari commented 8 years ago

Usecase: Two Way binding is required Value of the field needs to be updated from an observable.

As of now, two way binding has been implemented using vanilla ObservableField. Hence, it has no functionality to bind to an Observable. Hence, the only way is to subscribe to the observable externally. However, that requires knowledge about lifecycle. Idea is to provide a bindingadapter: bind:lifecycle_to: @{vm}, between View and any instance of Connectable interface.

The adapter will add an AttachStateListener and invoke connect when the view is subscribed and unsubscribe when view is detached.

ViewModels can then implement connect method and do the subscriptions there.

Not Functional any more 😭

Of course, it would be ideal to find a way around this. Allowing subscriptions in ViewModels would make the pattern weak, as it won't enforce functionalness constraint.

As twowaybinding behaviour is analogous to BehaviourSubject, it can be used to allow creating ObservableFields from observables while still allowing two way binding. Need to think about all edge cases here. If this works, this would be the way to go.

Looking for other usecases

Although subject could solve the top usecase, there could be other usecases, where subscribing cannot be moved to binding layer.

szantogab commented 7 years ago

@manas-chaudhari Any update on implementing ViewModel lifecycle ?

manas-chaudhari commented 7 years ago

No update on this. You can implement this through a BindingAdapter. It should roughly look like this:

public static void bindLifecycle(View view, Connectable connectable) {
    if (connectable != null) {
        newListener = new OnAttachStateChangeListener() {
                          private Subscription subscription;
                          @Override
                          public void onViewAttachedToWindow(View v) {
                              subscription = connectable.connect();
                          }

                          @Override
                          public void onViewDetachedFromWindow(View v) {
                              subscription.unsubscribe();
                          }
                      };
    }
    // TODO: remove old listener using ListenerUtil
    if (newListener != null) {
        view.addOnAttachStateChangeListener(newListener);
    }
}

Then you can make your ViewModel implement Connectable interface.

I haven't tried this myself. But I think this should work.

szantogab commented 7 years ago

Really nice, with this we are now able to automatically unsubscribe from Subscriptions. But what if I'd like to unsubscribe in onPause() and then subscribe again in onResume() ? For example I would like to stop scanning for BLE devices in onPause(), and then restart it.

I'm starting to get the feeling that we will end up wiring the MvvmActivity's lifecycle to the ViewModel.

manas-chaudhari commented 7 years ago

Adding https://github.com/manas-chaudhari/android-mvvm/pull/42 for reference.

manas-chaudhari commented 7 years ago

An approach to access view lifecycle in ViewModels was discussed in https://github.com/manas-chaudhari/android-mvvm/pull/42#issuecomment-268178199. Idea is to pass lifecycle to ViewModel as a dependency. NaviComponent can be used to represent lifecycle.

It would be interesting to create a documentation/example for this approach. @szantogab Would you like to take this up?