问题
Scenario
I have a quizz generator, which generates a sequence of quizzes of different classes. The sequence is of unlimited length.
There is a view model for the quizz generator. There is a view model for each type of a quizz. The quizz generator view model should create the view models of the quizzes depending on their classes.
Issue
A view model must not hold a reference to the lifecycle, but I need the lifecycle to create view models.
ViewModelProviders.of(lifecycle).get(classForQuizzType);
Questions
Where do I create the sub view models of the quizzes?
The one solution I can think of is to inject the sub view model from the activity each time. This is a detour, especially if nested views are involved.
The other solution is to create the view model in the nested view, which seems ugly either, as it is not common practice to access the lifecycle from inside a view.
If there is no clean solution, what's wrong with my approach of architecture? Should I use fragments for this kind of scenario?
回答1:
I give a first answer by myself inspired by pskink
. Maybe I will update the answer after some experiences with the suggested approach.
If a view model shall generate child objects aka components, the components don't need to be bound to the lifecycle themselves as long a the master object is bound to the lifecycle and the components are referenced from the master.
For the given example this means a good place to create the master object is in the top level position of the activity, where the lifecycle
is directly available. The quizz objects are referenced from the master objects. They don't need direct access to the lifecycle and may be created anywhere, for example inside the master object. This enables to create them on demand.
The components may or may not be a subclass of ViewModel
. I think it a good practice to extend ViewModel
. This parent class brings in the onCleared
method. This is the place to remove observers from the underlying model. Without doing this, you likely create memory leaks.
You have to take care to call onCleared
at the right moments, at least from the onCleared
method of the master object. In this special case each previous quizz has to be cleared just before a new quizz is generated, to remove the references from the underlying quizz models.
The view models of the components can simply be created using the new
keyword. There is no need to use factory or a provider.
回答2:
If there is no clean solution, what's wrong with my approach of architecture? Should I use fragments for this kind of scenario?
Yes, fragments are the right choice
Summary:
- There is no real alternative to binding
Views
byLiveData
. - If using
LiveData
aLifeCycle
is required. - If the lifecycle of a sub views in a sequence should be shorter than the lifecycle of the activity, then fragments are the way to go.
Details
There is no real alternative to binding Views
by LiveData
.
View models should not hold unterminated references to views, else the views exists as long as the view model exists causing memory leaks. There are three observer patterns to discuss how views could observe view models.
a.) MutableLiveData
They require a lifecycle. The references are cleaned up automatically, when the lifecycle ends. This is the recommended solution.
b.) WeakReferences
In theory this should work. The weak reference should be cleaned up by the garbage collector, when the last hard reference to the view is gone. In practice the solution is unstable and references sometime go away prematurely.
c.) Handmade observer
A handmade observer must call a remove method. Unfortunately there is no defined destruction hook, when a view goes away. There is no place to call the remove method in a view.
As a result a.) is the only possible solution according to my experience.
As a lifecycle is required for LiveData fragments are the way to go
The sub views mentioned here are created in a sequence. If we would bind them to the activity, they would pile up until the activity goes away although they are only needed for a small interval of time in sequence.
Fragments can exist for a subpart of the time of the activity. They are the right solution to bind the sub views of the sequence to them.
Example code
The quizzes are called challenges here. The FragmentManger
is always that of the activity, while the LifecycleOwner
is either the activity or a fragment.
# A view model acceptor interface for views
public interface ViewModelAcceptor<T extends ViewModel> {
void plugViewModel(
T viewModel,
LifecycleOwner lifecycleOwner,
FragmentManager fragmentManager
);
}
# In the parent view class of the challenges new challenges are created
# in sequence
ChallengeFragment challengeFragment = new ChallengeFragment();
challengeFragment.setChallengeViewModel(challengeViewModel);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(this.getId(), challengeFragment);
fragmentTransaction.commit();
# ChallengeFragment
public class ChallengeFragment extends Fragment {
private ChallengeViewModel challengeViewModel;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
return new ChallengeView(getActivity(), null);
}
public void setChallengeViewModel(ChallengeViewModel challengeViewModel) {
this.challengeViewModel = challengeViewModel;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ChallengeView challengeView = (ChallengeView) getView();
Objects.requireNonNull(challengeView)
.plugViewModel(challengeViewModel, this, getFragmentManager());
}
}
# Challenge views are the child views of the sequence
public class ChallengeView extends ConstraintLayout implements ViewModelAcceptor<ChallengeViewModel> {
[...]
}
来源:https://stackoverflow.com/questions/49894570/how-to-generate-composed-view-models-with-temporary-sub-views