Search code examples
androidandroid-architecture-components

Is it possible to pass an id parameter to AndroidViewModel in a public method without Factory?


I was looking at the BasicSample app from Android Architecture components sample. In the ProductViewModel.java file, some comments read:

It's not actually necessary in this case, as the product ID can be passed in a public method.

Based on my understanding of the comment, I would like to know if it's possible to pass the productId to the ProductViewModel without using a factory, and how this can be done.

I have implemented Transformation, and I know I can pass a productId using switchMap. But I was looking for a way to initialize the model with a single id.

public class ProductViewModel extends AndroidViewModel {

    private final LiveData<ProductEntity> mObservableProduct;

    public ObservableField<ProductEntity> product = new ObservableField<>();

    private final int mProductId;

    private final LiveData<List<CommentEntity>> mObservableComments;

    public ProductViewModel(@NonNull Application application, DataRepository repository,
            final int productId) {
        super(application);
        mProductId = productId;

        mObservableComments = repository.loadComments(mProductId);
        mObservableProduct = repository.loadProduct(mProductId);
    }

....

   /**
     * A creator is used to inject the product ID into the ViewModel
     * <p>
     * This creator is to showcase how to inject dependencies into ViewModels. It's not
     * actually necessary in this case, as the product ID can be passed in a public method.
     */
    public static class Factory extends ViewModelProvider.NewInstanceFactory {

        @NonNull
        private final Application mApplication;

        private final int mProductId;

        private final DataRepository mRepository;

        public Factory(@NonNull Application application, int productId) {
            mApplication = application;
            mProductId = productId;
            mRepository = ((BasicApp) application).getRepository();
        }

        @Override
        public <T extends ViewModel> T create(Class<T> modelClass) {
            //noinspection unchecked
            return (T) new ProductViewModel(mApplication, mRepository, mProductId);
        }
    }
}

Solution

  • In reference to the sample, when the comment says:

    the product ID can be passed in a public method

    this is referring to the fact you can create a public setter method.

    Since the productId is used to get a LiveData from your database, you should use a switchMap, as you mentioned. This is because switchMap allows you to lookup and update what a LiveData is pointing to, without needing to re-setup observers. If you didn't use a switchMap, you'd need to tell your Activity to observe the newly looked-up LiveData, and potentially stop observing the old LiveData object. More description of this is included in the docs.

    One more note - the factory is also useful here because you're passing in or injecting the DataRepository dependency via the constructor. This is a preferable way to get the repository into the class because it's easy to mock the repository when testing.

    With that in mind, if you wanted to do this would a factory, your code might look something like:

    public class ProductViewModel extends AndroidViewModel {
    
        private final LiveData<ProductEntity> mProduct;
        private final LiveData<List<CommentEntity>> mComments;
        private final MutableLiveData<Integer> mProductId = new MutableLiveData<>();
    
    
        public ProductViewModel(@NonNull Application application) {
            super(application);
    
            // Have some way to get your repository, this is not great for testing...
            Repository repository = ((BasicApp) application).getRepository();
    
                mProduct = Transformations.switchMap(mProductId, id -> {
                return repository.loadProduct(id);
            }
    
                mComments = Transformations.switchMap(mComments, id -> {
                return repository.loadComments(id);
            }
        }
    
        public void setProductId(int productId) {
           mProductId.setValue(productId); // This will trigger both of those switchMap statements
        }
    
    }