Android MVVM architecture and observing changes on data from an API

android mvvm best practices
mvvm login example android
android mvvm livedata
android mvvm form validation
android mvvm retrofit livedata
mvvm architecture android tutorial
android viewmodel livedata
android mvvm retrofit example

I'm new to the Android MVVM architecture. I have an API running locally with data ("deals") in it. I'd like to simply make a request to the API and display that data in a text field. Currently the data does not show up when the fragment is first loaded, but if I go to another activity and then back to the fragment it loads.

There are 3 classes of importance here.

DashboardViewModel.java:

package com.example.android_client.ui.dashboard;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

import com.example.android_client.models.Deal;
import com.example.android_client.repository.Repository;

import java.util.List;

public class DashboardViewModel extends ViewModel {

    private MutableLiveData<String> mText;
    private Repository repository;
    private MutableLiveData<List<Deal>> deals = null;

    public void init() {
        if(this.deals == null) {
            this.repository = Repository.getInstance();
            this.deals = this.repository.getDeals();
        }
    }

    public DashboardViewModel() {
        this.mText = new MutableLiveData<>();
    }

    public LiveData<List<Deal>> getDeals() {
        return this.deals;
    }
}

DashboardFragment.java:

package com.example.android_client.ui.dashboard;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;

import com.example.android_client.R;
import com.example.android_client.models.Deal;

import java.util.List;

public class DashboardFragment extends Fragment {

    private DashboardViewModel dashboardViewModel;

    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_dashboard, container, false);
        final TextView textView = root.findViewById(R.id.text_dashboard);
        dashboardViewModel = ViewModelProviders.of(this).get(DashboardViewModel.class);
        dashboardViewModel.init();
        dashboardViewModel.getDeals().observe(this, new Observer<List<Deal>>() {
            @Override
            public void onChanged(List<Deal> deals) {
                if (deals != null && !deals.isEmpty()) {
                    System.out.println(deals.get(0).toString());
                    textView.setText(deals.get(0).toString());
                }
            }
        });
        return root;
    }
}

and Repository.java:

package com.example.android_client.repository;

import androidx.lifecycle.MutableLiveData;

import com.example.android_client.models.Deal;
import com.google.gson.Gson;

import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

public class Repository {

    private static Repository instance;
    private ArrayList<Deal> dealsList = new ArrayList<>();
    private final OkHttpClient client = new OkHttpClient();

    public static Repository getInstance() {
        if(instance == null) {
            instance = new Repository();
        }
        return instance;
    }

    private Repository() {}

    public MutableLiveData<List<Deal>> getDeals() {
        setDeals();
        MutableLiveData<List<Deal>> deals = new MutableLiveData<>();
        deals.setValue(dealsList);
        return deals;
    }

    private void setDeals() {
        Request request = new Request.Builder()
                .url("http://10.0.2.2:8000/api/deals?<params here>")
                .build();

        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(@NotNull Call call, @NotNull IOException e) {
                e.printStackTrace();
            }

            @Override
            public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                try (ResponseBody responseBody = response.body()) {
                    if (!response.isSuccessful()) {
                        throw new IOException("Unexpected code " + response);
                    }
                    String jsonDeals = responseBody.string(); // can only call string() once or you'll get an IllegalStateException
                    Deal[] deals = new Gson().fromJson(jsonDeals, Deal[].class);
                    dealsList = new ArrayList<>(Arrays.asList(deals));
                }
            }
        });

    }
}

When stepping through the code in the Repository class I can see that setDeals() is called when I load the fragment, and the request in the callback is queued. The first time getDeals() returns, it returns a list of 0 deals (within the MutableLiveData object).

onResponse in the callback doesn't run until the fragment is already loaded. When debugging I can see that the data is in the objects (all the Gson stuff works fine), but onChanged doesn't get called again (which sets the text view).

Am I not observing changes on the deals properly?

Your code is not working due to a new live data instance be created whenever getDeals() is called and the api response value be informed to other live data instance. You must set api response value to same instance of MutableLiveData returned by getDeals()

I'm not saying that it is the best architectural solution, but if you create a mutable live data as a class attribute and return it whenever getDeals() is called. Probably, it's going to work.

Also, a good practice is return a LiveData and not a MutableLiveData to not allowing a external component modify the internal value.

Please, take a look at the piece of code below.

OBS: Maybe, there is some syntax error, because I have not compiled it

    import com.example.android_client.models.Deal;
    import com.google.gson.Gson;
    import org.jetbrains.annotations.NotNull;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;

    import okhttp3.Call;
    import okhttp3.Callback;
    import okhttp3.OkHttpClient;
    import okhttp3.Request;
    import okhttp3.Response;
    import okhttp3.ResponseBody;

        public class Repository {

        private static Repository instance;
        private ArrayList<Deal> dealsList = new ArrayList<>();
        private final OkHttpClient client = new OkHttpClient();
        private MutableLiveData<List<Deal>> _deals = new MutableLiveData<>();                         
        private LiveData<List<Deal>> deals = _deals


        public static Repository getInstance() {
            if(instance == null) {
                instance = new Repository();
            }
            return instance;
        }

        private Repository() {}

        public LiveData<List<Deal>> getDeals() {
            setDeals();
            return deals;
        }

        private void setDeals() {
            Request request = new Request.Builder()
                    .url("http://10.0.2.2:8000/api/deals?<params here>")
                    .build();

            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(@NotNull Call call, @NotNull IOException e) {
                    e.printStackTrace();
                }

                @Override
                public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
                    try (ResponseBody responseBody = response.body()) {
                        if (!response.isSuccessful()) {
                            throw new IOException("Unexpected code " + response);
                        }
                        String jsonDeals = responseBody.string(); // can only call string() once or you'll get an IllegalStateException
                        Deal[] deals = new Gson().fromJson(jsonDeals, Deal[].class);
                        dealsList = new ArrayList<>(Arrays.asList(deals));
                        _deals.setValue(dealsList);

                    }
                }
            });

        }

}
When 

LiveData Clean Code using MVVM and Android Architecture , Android Architecture Components is a new collection of libraries to help The ViewModel class allows data to survive configuration changes such as screen rotations ViewModel doesn't have lifecycle owner, to observe livedata inside Suppose activity needs to tell viewmodel to load data from api. I'm new to the Android MVVM architecture. I have an API running locally with data ("deals") in it. I'd like to simply make a request to the API and display that data in a text field. Currently the data does not show up when the fragment is first loaded, but if I go to another activity and then back to the fragment it loads.

in your repository class in function get deals. you are initializing live data. requesting url in background thread and posting value on live data which is not received from server yet.

to solve this create livedata instance in constructor of repository and postvalue on livedata in onResponse callback.

//sorry for bad writting, posted from mobile.

MVVM Architecture & LiveData, ViewModel, LifeCycle Components , Google has introduced android architecture components which We can wrap around an object to LiveData and start listening for data changes. this would be in ui layergetObservableProject().observe(this, new Observer<Object>() { In the Above Example News object is being fetched by an api call� MVVM LiveData. As said above, LiveData is one of the newly introduced architecture components. LiveData is an observable data holder. This allows the components in your app to be able to observe LiveData objects for changes without creating explicit and rigid dependency paths between them.

I think this would help. Try postValue on MutableLiveData in onResponse of network call. Please change your repository class like below:

package com.example.android_client.repository;
import androidx.lifecycle.MutableLiveData;

import com.example.android_client.models.Deal;
import com.google.gson.Gson;

import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;

public class Repository {

private static Repository instance;
private ArrayList<Deal> dealsList = new ArrayList<>();
private final OkHttpClient client = new OkHttpClient();
MutableLiveData<List<Deal>> deals = new MutableLiveData<>();

public static Repository getInstance() {
    if(instance == null) {
        instance = new Repository();
    }
    return instance;
}

private Repository() {}

private MutableLiveData<List<Deal>> getDeals() {
    Request request = new Request.Builder()
            .url("http://10.0.2.2:8000/api/deals?<params here>")
            .build();

    client.newCall(request).enqueue(new Callback() {
        @Override
        public void onFailure(@NotNull Call call, @NotNull IOException e) {
            e.printStackTrace();
        }

        @Override
        public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
            try (ResponseBody responseBody = response.body()) {
                if (!response.isSuccessful()) {
                    throw new IOException("Unexpected code " + response);
                }
                String jsonDeals = responseBody.string(); // can only call string() once or you'll get an IllegalStateException
                Deal[] deals = new Gson().fromJson(jsonDeals, Deal[].class);
                dealsList = new ArrayList<>(Arrays.asList(deals));
                deals.postValue(dealsList);
            }
        }
    });
 return deals;
}
}

MVVM architecture, ViewModel and LiveData (Part 1), During Google I/O, Google introduced architecture components which includes which facilitates developing Android app using MVVM pattern. pattern is to observe (or subscribe to) a ViewModel observable to get data in to be able to observe LiveData objects for changes without creating explicit and� By using Android architecture patterns using MVVM, architecture patterns say Model-View-ViewModel for your Android app development can be easily maintainable as everything is well organized and also changes can be dome very quickly. The code can be tested easily with MVVM Architecture as all modules are independent and testable.

Free Live Coronavirus Cases API With LiveData and MVVM, We'll build an Android Application which will show the live cases of Coronavirus in RecyclerView. We'll follow the MVVM Architecture Pattern, and implement LiveData LiveData is an observable data holder class which means that the it's aware of the relevant lifecycle status changes while observing. Once changes are triggered in data object, ui will inflate those changes. news_list_item is XML for News Item and bound to Article object. Conclusion It’s good to have an architecture in place, helps to keep code modularised , components decoupled and make testing easier.

Live data, ViewModel, Retrofit Android Architecture Component, The ViewModel class allows data to survive configuration changes such android architecture component LiveData, ViewModel with Retrofit2. network request for hitting API and using LiveData for observing API response. Adding Android’s Architecture Components. So far, you’ve seen the main MVVM components and discussed testing them. But what about the Binder layer? The Android SDK provides Data Binding to implement a Binder. Using DataBinding. Data binding is a technique that connects data sources with consumers and keeps them in sync.

Loading Asynchronous Data Using Android Architecture , Using Android Architecture Components Live Data for Asynchronous API or Fragment lifecycle events and configuration changes automatically. Github API using ViewModel class with LiveData that is observed by the� Android Architecture Components Part of Android Jetpack. Android architecture components are a collection of libraries that help you design robust, testable, and maintainable apps. Start with classes for managing your UI component lifecycle and handling data persistence.

Comments
  • Oops! Yes you are right. If I just take out that MutableLiveData<List<Deal>> deals = new MutableLiveData<>(); in getDeals it works. Thank you. Also thanks for the tip regarding when to use LiveData