One option would be to extend from AndroidViewModel instead, which has a reference to the Application Context. You can then use that Context
to load the string resources and deliver them back to your Activity
.
public class MyViewModel extends AndroidViewModel {
private final LiveData<String> stringResource = new MutableLiveData<>();
public MyViewModel(Application context) {
super(context);
statusLabel.setValue(context.getString(R.string.labelString));
}
public LiveData<String> getStringResource() {
return stringResource;
}
}
However, as it is pointed out in this Android Developers Medium Post by Jose Alcerreca, this is not the recommended practice because if, for example, the Locale
changes and the Activity gets rebuilt, the ViewModel will not react to this configuration change and will keep delivering the obsolete strings (from the previous Locale
).
Therefore, the suggested approach is to only return the resources ids from the ViewModel and get the strings on the Activity.
public class MyViewModel extends ViewModel {
public final LiveData<Integer> stringResource = new MutableLiveData<>();
public MyViewModel(Application context) {
super(context);
stringResource.setValue(R.string.labelString);
}
public LiveData<Integer> getStringResource() {
return stringResource;
}
}
UPDATE
Since you must get the string resources from your Activity
but apply the sorting logic in your ViewModel
, I don't think you can't avoid passing the List<String>
back to your ViewModel
to be sorted:
public class MyViewModel extends ViewModel {
public final MutableLiveData<Integer> stringArrayId = new MutableLiveData<>();
public MyViewModel(Application context) {
super(context);
stringArrayId.setValue(R.array.string_array_id);
}
public LiveData<Integer> getStringArrayId() {
return stringArrayId;
}
}
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
viewModel.getStringArrayId().observe(this, strArrayId -> {
String[] resolvedStrings = getResources().getStringArray(strArrayId);
List<String> sortedStrings = viewModel.sortList(Arrays.asList(resolvedStrings));
updateUi(sortedStrings);
});
}
}
If you think that's not MVVM'ish enough, maybe you can keep resolved List<String>
in your ViewModel
and have an extra LiveData
with the sorted list, that will be updated every time the LiveData
holding the original string list changes.
public class MyViewModel extends ViewModel {
public final MutableLiveData<Integer> stringArrayId = new MutableLiveData<>();
public final MutableLiveData<List<String>> stringsList = new MutableLiveData<>();
public final LiveData<List<String>> sortedStringList;
public MyViewModel(Application context) {
super(context);
stringArrayId.setValue(R.array.string_array_id);
sortedStringList = Transformations.map(stringsList, l -> {
Collections.sort(l);
return l;
});
}
public LiveData<Integer> getStringArrayId() {
return stringArrayId;
}
public LiveData<List<String>> sortedStringList() {
return sortedStringList;
}
public void setStringsList(List<String> resolvedStrings) {
stringsList.setValue(resolvedStrings);
}
}
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
viewModel.getStringArrayId().observe(this, strArrayId -> {
String[] resolvedStrings = getResources().getStringArray(strArrayId);
viewModel.setStringsList(Arrays.asList(resolvedStrings));
});
viewModel.sortedStringList().observe(this, sortedStrings -> updateUi(sortedStrings));
}
}
It feels over-engineered to me, and you still have to send the List<String>
back to your ViewModel
. However, having it this way might help if the sorting order depends on a Filter that can change during runtime. Then, you can add a MediatorLiveData
to react either when the Filter changes or the list of Strings changes, then your view only have to inform those changes to the ViewModel
and will observe the sorted list.