Michael Hsieh
Michael Hsieh

Reputation: 127

Get item by ID using ViewModel

In my MainActivity, I have items displayed in an Adapter. These items are Room Entity objects. When I click an item in the Adapter I start a DetailActivity.

Now in this DetailActivity, I'd like to get the clicked item using a ViewModel and the Entity object's ID.

My problem is, I'm not sure how to do this. Should I use LiveData? I'm confused because examples like this Google Codelab always wrap Entity objects in LiveData, so to get the object itself you have to observe changes with onChanged and use the method's parameter.

My current approach is to use Intent putExtra in MainActivity to send the item's ID to DetailActivity:

/** Called when an item in the places list is clicked.
 *
 * @param view The view displaying place name and address
 * @param position The position of the place item
 */
@Override
public void onItemClick(View view, int position) {
    // start DetailActivity
    Intent intent = new Intent(this, DetailActivity.class);
    intent.putExtra(EXTRA_PLACE_ID, adapter.getItem(position).getPlaceId());
    // get the position that was clicked
    // This will be used to save or delete the place from the DetailActivity buttons
    clickedPlacePos = position;

   
    startActivityForResult(intent, DETAIL_ACTIVITY_REQUEST_CODE);
}

Then in DetailActivity, after getting the ID, get Entity object by ID with ViewModel:

viewModel = new ViewModelProvider(this).get(PlaceViewModel.class);

Intent intent = getIntent();
if (intent.hasExtra(EXTRA_PLACE_ID)) {
    String id = intent.getStringExtra(EXTRA_PLACE_ID);

    viewModel.getPlaceById(id).observe(this, new Observer<PlaceModel>() {
        @Override
        public void onChanged(PlaceModel placeModel) {
            // placeModel will be null if place is deleted
            if (placeModel != null) {
                place = placeModel;
                // rest of code using this object is put here
            }
        }
    });
}

Is this the usual approach to get a single item by ID? Or is there a simpler way?

It seems sort of complicated, and also all code that uses this object has to be placed inside onChanged or else the object will be null. Also the Context of that code will no longer be DetailActivity. I'm new to Android.

The full code is here.


PlaceDao method:

@Query("SELECT * FROM place_table WHERE place_id =:id")
LiveData<PlaceModel> getPlaceById(String id);

PlaceRepository method:

LiveData<PlaceModel> getPlaceById(String id) {
    return placeDao.getPlaceById(id);
}

PlaceViewModel:

import com.michaelhsieh.placetracker.model.PlaceModel;

import java.util.List;

import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;

public class PlaceViewModel extends AndroidViewModel {

    private PlaceRepository repository;

    private LiveData<List<PlaceModel>> allPlaces;


    public PlaceViewModel(@NonNull Application application) {
        super(application);
        repository = new PlaceRepository(application);
        allPlaces = repository.getAllPlaces();
    }

    public LiveData<List<PlaceModel>> getAllPlaces() {
        return allPlaces;
    }

    public LiveData<PlaceModel> getPlaceById(String id) {
        return repository.getPlaceById(id);
    }

    public void insert(PlaceModel place) {
        repository.insert(place);
    }

    public void delete(PlaceModel place) {
        repository.delete(place);
    }

    public void update(PlaceModel place) {
        repository.update(place);
    }

}

Upvotes: 1

Views: 2505

Answers (1)

EpicPandaForce
EpicPandaForce

Reputation: 81559

Is this the usual approach to get a single item by ID? Or is there a simpler way?

Usual approach yes, actually intended, modern way of doings things in Jetpack way no. But it's no surprise, Google didn't update most of their docs, and they didn't update most of their example resources in this regard.

Anyway, the intended way to do this is to rely on the SavedStateHandle, and Transformations.switchMap.

viewModel = new ViewModelProvider(this).get(PlaceViewModel.class);

// Intent intent = getIntent();
// if (intent.hasExtra(EXTRA_PLACE_ID)) {
//     String id = intent.getStringExtra(EXTRA_PLACE_ID);

 viewModel.getPlace().observe(this, new Observer<PlaceModel>() {
    @Override
    public void onChanged(PlaceModel placeModel) {
        // placeModel will be null if place is deleted
        if (placeModel != null) {
            place = placeModel;
            // rest of code using this object is put here
        }
    }
 });

And

public class PlaceViewModel extends AndroidViewModel {

    private PlaceRepository repository;

    //private LiveData<List<PlaceModel>> allPlaces;
    private LiveData<PlaceModel> place;


    public PlaceViewModel(@NonNull Application application, @NonNull SavedStateHandle savedStateHandle) { // <-- activity 1.1.0+, lifecycle 2.2.0+
        super(application);
        repository = new PlaceRepository(application);
        //allPlaces = repository.getAllPlaces();
        place = Transformations.switchMap(savedStateHandle.<String>getLiveData(DetailActivity.EXTRA_PLACE_ID), (id) -> repository.getPlaceById(id));
    }

    //public LiveData<List<PlaceModel>> getAllPlaces() {
    //    return allPlaces;
    //}

    //public LiveData<PlaceModel> getPlaceById(String id) {
    //    return repository.getPlaceById(id);
    //}

    public LiveData<PlaceModel> getPlace() {
        return place;
    }

    public void insert(PlaceModel place) {
        repository.insert(place);
    }

    public void delete(PlaceModel place) {
        repository.delete(place);
    }

    public void update(PlaceModel place) {
        repository.update(place);
    }

}

Upvotes: 1

Related Questions