For my activity I need to know the connectivity state of the phone. Since this is the data that my UI needs to react to, it falls into a Model realm of MVVM (please let me know if you disagree).
To not execute code when not needed, my model code is currently subscribing to phone connectivity changes in onCreate()
and de-registers in onDestroy()
by implementing LifecycleObserver
To do so, I instantiate and wire up my model with viewModel inside of the Activity code.
Somehow it feels wrong.
In the ideal world, Activity would be part of a View layer (V in MVVM) and should only know about the viewModel, but in the case above the lifecycle-awareness makes an activity to know about Model too.
So, is a lifecycle-aware model a proper concept? Or should I re-think the design?
I really like that pattern. An example of implementing state listening in a global sharable object is
public class WifiState {
private static WifiState instance;
public static synchronized WifiState getInstance(Context context) {
if (instance == null) instance = new WifiState(context.getApplicationContext());
return instance;
}
private final Context context;
private final WifiManager wm;
private WifiState(Context context) {
this.context = context;
wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
}
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
wifiStateLiveData.setValue(state);
}
};
public LiveData<Integer> wifiState() { return wifiStateLiveData; }
private final MutableLiveData<Integer> wifiStateLiveData = new MutableLiveData<Integer>() {
@Override
protected void onActive() {
setValue(wm.getWifiState()); // update immediately
context.registerReceiver(receiver, new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION));
}
@Override
protected void onInactive() {
context.unregisterReceiver(receiver);
}
@Override
public void setValue(Integer value) { // debounce non-change
Integer old = getValue();
if (old == null && value != null || old != null && !old.equals(value)) {
super.setValue(value);
}
}
};
}
That allows you to use the same source from multiple places at once without the overhead of creating multiple broadcast receivers etc
public class SomeActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
WifiState.getInstance(this).wifiState().observe(this, state -> {
if (state != WifiManager.WIFI_STATE_ENABLED) {
Toast.makeText(this, "Please enable WIFI", Toast.LENGTH_LONG);
}
});
}
}
The big advantage of using the Lifecycle capabilities is that you don't have to litter your code with onStart / onStop calls, plus you can no longer miss onStop calls and leak receivers for example. The magic happens in some library and your code can become fairly simple and clean. Whether you implement LifecycleObserver
or use LiveData
, when done right, you end up with cleaner code.
Letting the framework handle the lifetime of your ViewModel
is also nice but it's not really changing much in terms of the MV* schema since you had a ViewModel one way or another before. You probably calculated properties in the Activity / Fragment directly. What's new here is that the framwork now retains that model for you for just the right amount of time. You could have done that with a retained Fragment before, and that's exactly what happens under the hood now.