Redberry Redberry - 18 days ago 7
Android Question

MVVM databinding when model is updated by service

SOLVED! See below for final solution

Problem:
I am trying to use databinding and MVVM for an android app but the problem lies in the fact that the model is updated by a service that is separate from the MVVM part of the app.
If I update the model via the viewmodel it works fine, but I can't seem to work out a clean way of getting a change to the model to bubble up to the viewmodel and then to the view when the model is updated by the service.

The App:
The app connects to an external device via bluetooth and then maintains a serial connection where sensor readings and settings are streamed continuously from the device to the app in close to real-time. Commands can also be sent to the device via this connection.
The model acts as an in-memory representation of the device. Different screens in the app expose different sections of the model to the user and may or may not allow them to adjust certain values (settings).

Code: (I can't show my exact code but here is a simplified version)

Model:

public class DeviceModel extends BaseObservable {

private int mode;

@Bindable
public int getMode(){
return mode;
}

public void setMode(int mode) {
this.mode = mode;
notifyPropertyChanged(BR.mode);
}
}


ViewModel:

public class BasicViewViewModel extends BaseObservable {
private final Context mContext;
private ObservableField<DeviceModel> modelFields;

public BasicViewViewModel(Context context){
mContext = context.getApplicationContext();
ApplicationBase app = ApplicationBase.getInstance();
modelFields = new ObservableField<>(app.dModel);
}

@Bindable
public String getMode(){
return modelFields.get().Mode;
}
}


Fragment:

public class BasicViewFragment extends Fragment {
private FragmentBasicViewBinding mBinding;
private BasicViewViewModel mViewModel;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState){
mViewModel = new BasicViewViewModel(getContext());
mBinding = DataBindingUtil.inflate(
inflater, R.layout.fragment_basic_view, container, false);
View view = mBinding.getRoot();
mBinding.setDevice(mViewModel);
return view;
}
}


Layout:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="device" type="com.example.android.bluetoothcomms.BasicViewViewModel"/>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:text="@{device.mode}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tvModeVal" />

</RelativeLayout>
</layout>


Custom Application Code:

public class ApplicationBase extends Application {

private static ApplicationBase instance;
public BluetoothCommsService btComms;
public DeviceModel dModel;


@Override
public void onCreate(){
super.onCreate();
instance = this;
dModel = new DeviceModel();
btComms = new BluetoothCommsService();

}

public static ApplicationBase getInstance(){
return instance;
}
}


SOLUTION
In the end I followed @exkoria 's advice and used EventBus to create and send a custom event:

public class DeviceModelUpdateEvent {

public final String fieldName;
public final String newValue;

public DeviceModelUpdateEvent(String field, String value){
fieldName = field;
newValue = value;
}
}


The I put a handler in my viewmodel to trigger the databinding update in the view:

@Subscribe
public void onMessageEvent(DeviceModelUpdateEvent event){
switch(event.fieldName){
case "yieldLevel":
notifyPropertyChanged(BR.yieldLevel);
break;
case "mode":
notifyPropertyChanged(BR.mode);
break;
case "deviceType":
notifyPropertyChanged(BR.deviceType);
break;
case "firmwareVersion":
notifyPropertyChanged(BR.firmwareVersion);
}
}

Answer

Can't you just add a listener to your model so your viewmodel can listen for changes in the model and update the view when changes occur? Or perhaps it's a nice usage for an event bus such as this one: https://github.com/greenrobot/EventBus.

Comments