Don't know if google has nice support for nested ViewModel's, looks like not.
Thankfully, we don't need to stick to androidx.lifecycle.ViewModel
to apply MVVM approach where we need. And there is a small example I decided to write:
Fragment, nothing changes:
@Override public void onCreate(@Nullable Bundle savedInstanceState) {
final ItemListAdapter adapter = new ItemListAdapter();
binding.getRoot().setAdapter(adapter);
viewModel = new ViewModelProvider(this).get(ItemListViewModel.class);
viewModel.getItems().observe(getViewLifecycleOwner(), adapter::submitList);
}
ItemListAdapter, in addition to populate view, it also becomes responsible for notifying item's observers - should they continue to listen, or not. In my example adapter was ListAdapter which extends RecyclerView.Adapter, so it receives list of items. This is unintentionally, just edited some code I already have. It's probably much better to use different base implementation, but it's acceptable for demonstration purposes:
@Override public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
return new Holder(parent);
}
@Override public void onBindViewHolder(Holder holder, int position) {
holder.lifecycle.setCurrentState(Lifecycle.State.RESUMED);
holder.bind(getItem(position));
}
@Override public void onViewRecycled(Holder holder) {
holder.lifecycle.setCurrentState(Lifecycle.State.DESTROYED);
}
// Idk, but these both may be used to pause/resume, while bind/recycle for start/stop.
@Override public void onViewAttachedToWindow(Holder holder) { }
@Override public void onViewDetachedFromWindow(Holder holder) { }
Holder. It implements LifecycleOwner, which allows to unsubscribe automatically, just copied from androidx.activity.ComponentActivity
sources so all should be okay :D :
static class Holder extends RecyclerView.Holder implements LifecycleOwner {
/*pkg*/ LifecycleRegistry lifecycle = new LifecycleRegistry(this);
/*pkg*/ Holder(ViewGroup parent) { /* creating holder using parent's context */ }
/*pkg*/ void bind(ItemViewModel viewModel) {
viewModel.getItem().observe(this, binding.text1::setText);
}
@Override public Lifecycle getLifecycle() { return lifecycle; }
}
List view-model, "classique" androidx-ish ViewModel, but very rough, also provide nested view models. Please, pay attention, in this sample all view-models start to operate immediately, in constructor, until parent view-model is commanded to clear! Don't Try This at Home!
public class ItemListViewModel extends ViewModel {
private final MutableLiveData<List<ItemViewModel>> items = new MutableLiveData<>();
public ItemListViewModel() {
final List<String> list = Items.getInstance().getItems();
// create "nested" view-models which start background job immediately
final List<ItemViewModel> itemsViewModels = list.stream()
.map(ItemViewModel::new)
.collect(Collectors.toList());
items.setValue(itemsViewModels);
}
public LiveData<List<ItemViewModel>> getItems() { return items; }
@Override protected void onCleared() {
// need to clean nested view-models, otherwise...
items.getValue().stream().forEach(ItemViewModel::cancel);
}
}
Item's view-model, using a bit of rxJava to simulate some background work and updates. Intentionally I do not implement it as androidx....ViewModel
, just to highlight that view-model is not what google names ViewModel but what behaves as view-model. In actual program it most likely will extend, though:
// Wow, we can implement ViewModel without androidx.lifecycle.ViewModel, that's cool!
public class ItemViewModel {
private final MutableLiveData<String> item = new MutableLiveData<>();
private final AtomicReference<Disposable> work = new AtomicReference<>();
public ItemViewModel(String topicInitial) {
item.setValue(topicInitial);
// start updating ViewModel right now :D
DisposableHelper.set(work, Observable
.interval((long) (Math.random() * 5 + 1), TimeUnit.SECONDS)
.map(i -> topicInitial + " " + (int) (Math.random() * 100) )
.subscribe(item::postValue));
}
public LiveData<String> getItem() { return item; }
public void cancel() {
DisposableHelper.dispose(work);
}
}
Few notes, in this sample:
- "Parent" ViewModel lives in activity scope, so all its data (nested view models) as well.
- In this example all nested vm start to operate immediately. Which is not what we want. We want to modify constructors, onBind, onRecycle and related methods accordingly.
- Please, test it on memory leaks.