Consuming REST on Android with Retrofit, ViewModel and LiveData


The first functionality we want to implement in the Ersa Android client is some basic dashboarding. We’ll display the latest temperature and humidity readings by origin (sensor node) as well as a calculated dew point value. A pager allows us to swipe between different origins. Data will be retrieved from our Ersa REST API and bound to a ViewModel that updates the fragments on display.

All code is available on GitHub:


We’ll use Retrofit to consume the API and a ViewModel from the Android Architecture Components to hold the data and update the UI. Values are displayed in Gauge views. The Ersa library is used for the needed calculations.

dependencies {
    // ...
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
    implementation ''
    implementation 'android.arch.lifecycle:extensions:1.1.0'
    implementation 'com.github.Pygmalion69:Gauge:1.2.3'
    implementation 'com.github.Pygmalion69:Ersa:0.2'
    // ...


Retrofit makes it extremely easy to consume REST. Let’s start with a POJO reflecting the entity from the API.

public class Reading {
    private Long id;
    private String origin;
    private Long timestamp;
    private Double temperature;
    private Double humidity;
    public Long getId() {
        return id;
    public void setId(Long id) { = id;
    public String getOrigin() {
        return origin;
    public void setOrigin(String origin) {
        this.origin = origin;
    public Long getTimestamp() {
        return timestamp;
    public void setTimestamp(Long timestamp) {
        this.timestamp = timestamp;
    public Double getTemperature() {
        return temperature;
    public void setTemperature(Double temperature) {
        this.temperature = temperature;
    public Double getHumidity() {
        return humidity;
    public void setHumidity(Double humidity) {
        this.humidity = humidity;

We need to define an interface for our endpoint:

    public interface ApiEndpointInterface {
        Call<List<Reading>> getLatestReadings();

Now we can build a Retrofit instance and create an API Service:

        String url = "http://services.yourdomain.tld:8080/ersa/";
        Retrofit retrofit = new Retrofit.Builder()
        final ApiEndpointInterface apiService =

Next we can create a Call and get our results in a Callback. In this case we’ll update a ViewModel from the Callback.

        Call<List<Reading>> call = apiService.getLatestReadings();
        call.enqueue(new Callback<List<Reading>>() {
            public void onResponse(@NonNull Call<List<Reading>> call, @NonNull Response<List<Reading>> response) {
                Log.d(TAG, response.toString());
                if (response.body() != null && mViewModel != null) {
            public void onFailure(@NonNull Call<List<Reading>> call, @NonNull Throwable t) {


We use a ViewModel to decouple our data from out business logic. Furthermore, the LiveData in our ViewModel is lifecycle-aware. It only updates Observers that are in the STARTED or the RESUMED state.

Let’s create two observable properties:

public class DashboardViewModel extends ViewModel {
    private MutableLiveData<List<Reading>> mReadings;
    private MutableLiveData<Integer> mNumberOfOrigins;
    public MutableLiveData<List<Reading>> getReadings() {
        if (mReadings == null) {
            mReadings = new MutableLiveData<>();
        return mReadings;
    public MutableLiveData<Integer> getNumberOfOrigins() {
        if (mNumberOfOrigins == null) {
            mNumberOfOrigins = new MutableLiveData<>();
        return mNumberOfOrigins;

– When there is a change in the readings data we want to update the UI.
– When there is a change in the number of origins we need to notify the pager adapter (as the number of dashboard fragments needs to change accordingly).

Here’s the Observer in our activity that notifies the adapter. Note that we set the activity (‘this’) as the lifecycle owner, so that the ViewModel is aware of its state. We should also do this in our fragments when we access the same ViewModel.

        mAdapter = new DashboardAdapter(getSupportFragmentManager());
        ViewPager pager = findViewById(;
        mViewModel = ViewModelProviders.of(this).get(DashboardViewModel.class);
        final Observer<Integer> originObserver = numberOfOrigins -> {
            if (mViewModel.getReadings() != null && mViewModel.getReadings().getValue() != null) {
                origins.clear(); // List<String>
                for (Reading reading : mViewModel.getReadings().getValue()) {
        mViewModel.getNumberOfOrigins().observe(this, originObserver);

In the dashboard fragment, we create an Observer to update the UI.

        mViewModel = ViewModelProviders.of(getActivity()).get(DashboardViewModel.class);
        final Observer<List<Reading>> readingsObserver = readings -> {
            if (readings != null) {
                for (Reading reading : readings) {
                    if (mOrigin.equals(reading.getOrigin())) {
                        mReading = reading;
        mViewModel.getReadings().observe(getActivity(), readingsObserver);


As we all know, Android can be a bitch. You might be familiar with the issue that UI fragments in a pager do not update when the dataset changes. So we create an adapter that assures UI updates in this case and an interface to be implemented by updatable fragments. See

public interface Updatable {
    void update();

public abstract class UpdatableFragmentStatePagerAdapter extends FragmentStatePagerAdapter {
    private WeakHashMap<Integer, Fragment> mFragments;
    public UpdatableFragmentStatePagerAdapter(FragmentManager fm) {
        mFragments = new WeakHashMap<>();
    public Fragment getItem(int position) {
        Fragment item = getFragmentItem(position);
        mFragments.put(position, item);
        return item;
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
        Integer key = position;
        if (mFragments.containsKey(key)) {
    public void notifyDataSetChanged() {
        for (Integer position : mFragments.keySet()) {
            //Make sure we only update fragments that should be seen
            if (position != null && mFragments.get(position) != null && position < getCount()) {
                updateFragmentItem(position, mFragments.get(position));
    public int getItemPosition(Object object) {
        //If the object is a fragment, check to see if we have it in the hashmap
        if (object instanceof Fragment) {
            int position = findFragmentPositionHashMap((Fragment) object);
            //If fragment found in the hashmap check if it should be shown
            if (position >= 0) {
                //Return POSITION_NONE if it shouldn't be displayed
                return (position >= getCount() ? POSITION_NONE : position);
        return super.getItemPosition(object);
     * Find the position of a fragment in the hashmap if it is being viewed
     * @param object the Fragment we want to check for
     * @return the position if found else -1
    private int findFragmentPositionHashMap(Fragment object) {
        for (Integer position : mFragments.keySet()) {
            if (position != null &&
                    mFragments.get(position) != null &&
                    mFragments.get(position) == object) {
                return position;
        return -1;
    public abstract Fragment getFragmentItem(int position);
    public abstract void updateFragmentItem(int position, Fragment fragment);

In our DashboardAdapter we override getFragmentItem() instead of getItem():

public static class DashboardAdapter extends UpdatableFragmentStatePagerAdapter {
        DashboardAdapter(FragmentManager fm) {
        public int getCount() {
            return origins.size();
        public Fragment getFragmentItem(int position) {
            return DashboardFragment.newInstance(origins.get(position));
        public void updateFragmentItem(int position, Fragment fragment) {
            if (fragment instanceof Updatable) {
                ((Updatable) fragment).update();

And finally we can update the fragments.

public class DashboardFragment extends Fragment implements Updatable {
    // ...
    public void update() {
        if (mViewCreated) {
            double relativeHumidity = mReading.getHumidity();
            double kelvin = mTemperature.getKelvin();
            try {
                mDewPoint.setKelvin(mDew.dewPoint(relativeHumidity, kelvin));
            } catch (SolverException e) {
            mTvDateTime.setText((new Date(1000 * mReading.getTimestamp()).toString()));
            mGaugeDewPoint.moveToValue((float) mDewPoint.getTemperature());