LiveData - not getting data in fragment on second call - android

I am using LiveData to get data from the server.
In the onResume method, Calling same function every after 5 second
I am able to get data only on the First API call.
Second time the observer is not triggered and not able to get data in the fragment.
this is my fragment:
private int delay = 5 * 1000;
private ViewModel mViewModel;
private DetailsModel details = new DetailsModel();
mViewModel = ViewModelProviders.of(this).get(ViewModel.class);
mViewModel.getDetailsResponse("token", "ids");
mViewModel.getData().observe(this, new Observer< DetailsModel >() {
#Override
public void onChanged(DetailsModel response) {
details = response;
}});
//getting data in every 5 seconds
#Override
public void onResume() {
super.onResume();
liveHandler.postDelayed(runnable = new Runnable() {
public void run() {
mViewModel. getDetailsResponse("token", "ids");
liveHandler.postDelayed(runnable, delay);
}
}, delay);
}
ViewModel.java
private MutableLiveData<DetailsModel> detailsResponse;
private ProjectRepository repository = new ProjectRepository();
public void getDetailsResponse(String token, String ids) {
detailsResponse = repository.getMapData("token", "ids");
}
public MutableLiveData<DetailsModel> getData() {
return detailsResponse;
}
ProjectRepository.java
public MutableLiveData<DetailsModel> getMapData(String token, String ids) {
final MutableLiveData<DetailsModel> responseMutableLiveData = new MutableLiveData<>();
Call<DetailsModel> call = service.getMapDetails(token, ids);
call.enqueue(new Callback<DetailsModel>() {
#Override
public void onResponse(#NonNull Call<DetailsModel> call, #NonNull Response<DetailsModel> response) {
responseMutableLiveData.postValue(response.body());
}
#Override
public void onFailure(#NonNull Call<DetailsModel> call, #NonNull Throwable t) {
t.printStackTrace();
}
});
return responseMutableLiveData;
}

Whenever you call getDetailsResponse, you create a new LiveData object, which is the problem, you should do this in your ProjectRepository
final MutableLiveData<DetailsModel> responseMutableLiveData = new MutableLiveData<>();
public MutableLiveData<DetailsModel> getMapData(String token, String ids) {
Call<DetailsModel> call = service.getMapDetails(token, ids);
call.enqueue(new Callback<DetailsModel>() {
#Override
public void onResponse(#NonNull Call<DetailsModel> call, #NonNull Response<DetailsModel> response) {
responseMutableLiveData.postValue(response.body());
}
#Override
public void onFailure(#NonNull Call<DetailsModel> call, #NonNull Throwable t) {
t.printStackTrace();
}
});
return responseMutableLiveData;
}
And in your VM:
private MutableLiveData<DetailsModel> detailsResponse = null;
private ProjectRepository repository = new ProjectRepository();
public void getDetailsResponse(String token, String ids) {
if (detailsResponse == null) {
detailsResponse = repository.getMapData("token", "ids");
} else {
// Just call it, you already assigned before
repository.getMapData("token", "ids");
}
}
public MutableLiveData<DetailsModel> getData() {
return detailsResponse;
}
So, basically move the object creation out of the function itself. However, the design of your MVVM implementation can be simplified a lot. I would urge to check some examples!

You are using postDelayed() twice, so it is not working. Change your onResume() code to below mentioned.
#Override
public void onResume() {
super.onResume();
liveHandler.postDelayed(runnable = new Runnable() {
public void run() {
mViewModel.getDetailsResponse("token", "ids");
liveHandler.post(runnable);
}
}, delay);
}

Related

Why retrofit returning Null 'LiveData' even after setting return value non-null correctly?

I got the response correctly, set value of LiveData correctly (got confirmed by printing value on console after setting value to live data). But When i tried to print same thing just before "return" it's giving me NullPointerException.
public class ProjectRepository {
private ProjectRepository instance;
Context context;
public ProjectRepository(Context context) {
this.context=context;
}
private MutableLiveData<List<PojoDivision>> data = new MutableLiveData<>();
public LiveData<List<PojoDivision>> getDivisionList() {
((RetrofitConfiguration)context).getDivisionRestApiWithAuthentication().getDivisionList().enqueue(new Callback<List<PojoDivision>>() {
#Override
public void onResponse(Call<List<PojoDivision>> call, Response<List<PojoDivision>> response) {
if (response.isSuccessful()) {
System.out.println(response.body().get(4).getName()); // this is printing
data.setValue(response.body());
System.out.println(data.getValue().get(4).getName()); // this is printing
}
}
#Override
public void onFailure(Call<List<PojoDivision>> call, Throwable t) {
Log.d(TAG, t.getMessage());
}
});
/*
following code is not printing with nullPointerException
java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object java.util.List.get(int)' on a null object reference
*/
System.out.println(data.getValue().get(4).getName());
return data;
}
}
Since you're using a LiveData object at the class level for your repository class, your method has no need to return anything. Instead it's responsibility is for updating that LiveData object.
public class ProjectRepository {
private static final String TAG = "ProjectRepository";
private ProjectRepository instance;
Context context;
public ProjectRepository(Context context) {
this.context = context;
}
public MutableLiveData<List<PojoDivision>> data = new MutableLiveData<>();
public void getDivisionList() {
((RetrofitConfiguration) context).getDivisionRestApiWithAuthentication().getDivisionList().enqueue(new Callback<List<PojoDivision>>() {
#Override
public void onResponse(Call<List<PojoDivision>> call, Response<List<PojoDivision>> response) {
if (response.isSuccessful()) {
data.postValue(response.body());
}
}
#Override
public void onFailure(Call<List<PojoDivision>> call, Throwable t) {
Log.d(TAG, t.getMessage());
}
});
}
}
The clients of this class would need to 1) observe the LiveData object, and 2) call the getDivisionList() method to trigger an update:
class MyFragment extends Fragment {
private ProjectRepository repository;
private void setRepository() {
Context context = getContext();
if (context == null) return;
repository = new ProjectRepository(context);
}
public void observeData() {
repository.data.observe(this, new Observer<List<PojoDivision>>() {
#Override
public void onChanged(List<PojoDivision> pojoDivisions) {
// do something with updated data
}
});
}
public void triggerUpdate() {
repository.getDivisionList();
}
}

call MutableLiveData web service agine to update the list

I use MVVM structure in my project.
I have the main fragment with list observed a web service as you can see in the code
fragment :
mViewModel.getHomePageList().observe(this, homeDataWrapper -> {
if (homeDataWrapper!=null) {
if (homeDataWrapper.isStatus()) {
binding.homeProgressBar.setVisibility(View.INVISIBLE);
ToastUtil.showTosat(homeDataWrapper.getData().getMessage(), getContext());
Log.d(TAG, "onChanged: ");
}
}
});
view model:
ublic class HomePageViewModel extends AndroidViewModel {
private MutableLiveData<DataWrapper<Home>> data;
public ObservableInt loading;
private HomeRepository homeRepository;
private HomePageAdapter adapter;
public HomePageViewModel(#NonNull Application application) {
super(application);
}
public void init() {
adapter = new HomePageAdapter(R.layout.main_page_list, this);
homeRepository = new HomeRepository();
if (this.data != null) {
// ViewModel is created per Fragment so
// we know the userId won't change
return;
}
data = homeRepository.getHomeScreen();
}
public HomePageAdapter getAdapter() {
return adapter;
}
public void onItemClick(Integer index) {
}
public void onSerachClicked(View view) {
Navigation.findNavController(view).navigate(R.id.action_homePageFragment_to_searchActivity);
}
public MutableLiveData<DataWrapper<Home>> getHomePageList() {
return this.data;
}
}
HomeRepository :
public MutableLiveData<DataWrapper<Home>> getHomeScreen() {
final MutableLiveData<DataWrapper<Home>> homeMutableLiveData = new MutableLiveData<>();
final DataWrapper<Home> dataWrapper = new DataWrapper<>();
RetrofitInstance.getApiService().getHome().enqueue(new Callback<Home>() {
#Override
public void onResponse(#NotNull Call<Home> call, #NotNull Response<Home> response) {
Log.d("", "onResponse: " + response);
if (response.code() == 200) {
dataWrapper.setData(response.body());
dataWrapper.setStatus(true);
homeMutableLiveData.postValue(dataWrapper);
}
}
#Override
public void onFailure(Call<Home> call, Throwable t) {
Log.d("", "onResponse: " + t);
dataWrapper.setApiException((Exception) t);
dataWrapper.setStatus(false);
homeMutableLiveData.postValue(dataWrapper);
}
});
return homeMutableLiveData;
}
I would like to add SwipeRefreshLayout to update the main list. what is the correct way to call the web service again and update the list?
can anyone help me?
You can just call getHomeScreen form your Repository class to trigger data pulling from the server again, after pulling request completed, the observers will be notified using the the MutableLiveData.
But here is your issue, you are creating a new MutableLiveData object each time you call getHomeScreen. Thus, the first one will not be notified and the list will not be updated!
To solve the problem you have to initialize your MutableLiveData somewhere else so it will not be created again every time you call getHomeScreen.
I suggest you make your HomeRepository class a singleton class and initialize the MutableLiveData object inside the constructor, then you can use this object to post data to observers once you got new data from the server.
public class HomeRepository {
private static HomeRepository instance;
private MutableLiveData<DataWrapper<Home>> homeMutableLiveData;
public static HomeRepository getInstance() {
if(instance == null) instance = new HomeRepository();
return instance;
}
private HomeRepository() {
homeMutableLiveData = new MutableLiveData<>();
}
public MutableLiveData<DataWrapper<Home>> getHomeScreen() {
final DataWrapper<Home> dataWrapper = new DataWrapper<>();
RetrofitInstance.getApiService().getHome().enqueue(new Callback<Home>() {
#Override
public void onResponse(#NotNull Call<Home> call, #NotNull Response<Home> response) {
Log.d("", "onResponse: " + response);
if (response.code() == 200) {
dataWrapper.setData(response.body());
dataWrapper.setStatus(true);
homeMutableLiveData.postValue(dataWrapper);
}
}
#Override
public void onFailure(Call<Home> call, Throwable t) {
Log.d("", "onResponse: " + t);
dataWrapper.setApiException((Exception) t);
dataWrapper.setStatus(false);
homeMutableLiveData.postValue(dataWrapper);
}
});
return homeMutableLiveData;
}
}
Inside onRefereshListener of fragment
swifeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
mViewModel.getHomeScreenDetail();
}
});
In Viewmodel create getHomeScreenDetail method
public void getHomeScreenDetail(){
data = homeRepository.getHomeScreen();
}

Live Data is not updating ViewModel

I am observing my viewmodel in an Activity:
private void setupViewModel() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
mSortCategory = sharedPreferences.getString(getString(R.string.pref_sort_key), getString(R.string.pref_sort_popular));
MainViewModelFactory factory = new MainViewModelFactory(mSortCategory);
mViewModel = ViewModelProviders.of(this, factory).get(MainViewModel.class);
mViewModel.getResponse().observe(this, new Observer<MovieResponse>() {
#Override
public void onChanged(MovieResponse movieResponse) {
mMovieAdapter.setData(movieResponse.getResults());
mCurrentPage = movieResponse.getPage();
mTotalPages = movieResponse.getTotalPages();
Log.d(TAG, "setupviewmodel: " + movieResponse.getResults().get(0).getOriginalTitle());
}
});
and here is my VM:
public class MainViewModel extends ViewModel {
private MutableLiveData<MovieResponse> mMoviesResponseLiveData;
public MainViewModel(String category) {
mMoviesResponseLiveData = Repository.getInstance().loadMoviesFromApi(category, 1);
}
public void loadMovies(String category, int currentPage) {
mMoviesResponseLiveData = Repository.getInstance().loadMoviesFromApi(category, currentPage);
}
public MutableLiveData<MovieResponse> getResponse() {
return mMoviesResponseLiveData;
}
and here I make a call to retrofit:
public MutableLiveData<MovieResponse> loadMoviesFromApi(String sort, int page) {
final MutableLiveData<MovieResponse> data = new MutableLiveData<>();
Call<MovieResponse> call = mApiService.getMoviesResponse(sort, BuildConfig.OPEN_WEATHER_MAP_API_KEY, page);
Log.d(TAG, "loadMoviesFromApi: " + call.request().url().toString());
call.enqueue(new Callback<MovieResponse>() {
#Override
public void onResponse(Call<MovieResponse> call, Response<MovieResponse> response) {
if (response.isSuccessful()) {
data.setValue(response.body());
}
}
#Override
public void onFailure(Call<MovieResponse> call, Throwable t) {
t.printStackTrace();
}
});
return data;
}
As I understand new data should be asynchronously loaded by calling a method from activity on Viewmodel :
private void updateUI() {
mViewModel.loadMovies(mSortCategory, mCurrentPage);
}
I receive data from retrofit. but for some reason Livedata is not being observed.
Your observer isn't notified because of this method:
public void loadMovies(String category, int currentPage) {
mMoviesResponseLiveData = Repository.getInstance().loadMoviesFromApi(category, currentPage);
}
loadMoviesFromApi returns a new instance of LiveData so the LiveData returned from getResponse is no longer referenced by your viewModel. If you want your observer to be notified you should return the ViewModel from method loadMovies and observe it or invoke mMoviesResponseLiveData.postValue(responseFromRetrofit)

How to use a specific query in Retrofit with Android?

I have an API interface which has two overloaded methods:
public interface Api {
#GET("movie/now_playing")
Call<ApiResponse> getMovies(#Query("page") int page);
#GET("search/movie")
Call<ApiResponse> getMovies(#Query("query") String query, #Query("page") int page);
}
First one is used to get now playing movies and the second one to search for movies.
In my MainActivity I use this code to display the data in a RecyclerView:
MovieViewModel movieViewModel = ViewModelProviders.of(this).get(MovieViewModel.class);
MovieAdapter adapter = new MovieAdapter(this);
movieViewModel.moviePagedList.observe(this, adapter::submitList);
recyclerView.setAdapter(adapter);
This is my MovieViewModel class:
public class MovieViewModel extends ViewModel {
LiveData<PagedList<ApiResponse.Movie>> moviePagedList;
public MovieViewModel() {
MovieDataSourceFactory movieDataSourceFactory = new MovieDataSourceFactory();
PagedList.Config config = new PagedList.Config.Builder().setEnablePlaceholders(false).setPageSize(20).build();
moviePagedList = new LivePagedListBuilder<>(movieDataSourceFactory, config).build();
}
}
And this my MovieDataSourceFactory class:
public class MovieDataSourceFactory extends DataSource.Factory<Integer, ApiResponse.Movie> {
private MutableLiveData<PageKeyedDataSource<Integer, ApiResponse.Movie>> movieLiveDataSource = new MutableLiveData<>();
#Override
public DataSource<Integer, ApiResponse.Movie> create() {
MovieDataSource movieDataSource = new MovieDataSource();
movieLiveDataSource.postValue(movieDataSource);
return movieDataSource;
}
}
And this is my MovieDataSource class:
public class MovieDataSource extends PageKeyedDataSource<Integer, ApiResponse.Movie> {
#Override
public void loadInitial(#NonNull LoadInitialParams<Integer> params, #NonNull LoadInitialCallback<Integer, ApiResponse.Movie> callback) {
Api api = RetrofitClient.getInstance().getApi();
Callback<ApiResponse> call = new Callback<ApiResponse>() {
#Override
public void onResponse(#NonNull Call<ApiResponse> call, #NonNull Response<ApiResponse> response) {
if(response.body() != null){
callback.onResult(response.body().movieList, null, page + 1);
}
}
#Override
public void onFailure(#NonNull Call<ApiResponse> call, #NonNull Throwable t) {}
};
api.getMovies(page).enqueue(call);
}
//loadBefore and loadAfter methods
}
If I run this code, everything works fine, the first query is invoked and I get the result correctly. The question is, how can I dynamically use one or the other?
api.getMovies(query, page).enqueue(call);
boolean globalVariableShouldUseGetMoviesCallWithOnlyOneArgument = true
private void doNetworkCallAndCheckBooleanStatus(){
if (globalVariableShouldUseGetMoviesCallWithOnlyOneArgument) {
getMovies(thisIsOneArgumentAsYouCanSee);
} else {
getMovies(thisIsUsingTwoArguments, asYouCanSee);
}
}
private void someOtherMethodInTheApp() {
globalVariableShouldUseGetMoviesCallWithOnlyOneArgument = false
doNetworkCallAndCheckBooleanStatus()
}

LiveData onChanged method called only first time

i have a simple app that polls a service to display country list in a recyclerview. Here i am using LiveData to update the recyclerview whenever there is any change in the country list. Trouble is, LiveData's onChanged method is invoked only the first time setValue is called. But after that if there are any further changes to the data the onChanged is not invoked??
Following is my code for more info -
CountryListFragment
//Setup observer
Observer<List<CountryModel>> myObserver = new Observer<List<CountryModel>>() {
#Override
public void onChanged(#Nullable List<CountryModel> countryModels) {
mCountryList = countryModels;
//Update recyclerview
myAdapter.updateRecyclerView(mCountryList);
}
};
//Set Observer for Viewmodel
countryViewModel.getmCountryList().observe(this,myObserver);
CountryViewModel
public class CountryViewModel extends AndroidViewModel {
private MutableLiveData> mCountryList;
private MyRetrofitClient myRetrofitClient;
public CountryViewModel(#NonNull Application application) {
super(application);
}
public void init(){
mCountryList = new MutableLiveData<>();
myRetrofitClient = new MyRetrofitClient();
**mCountryList = myRetrofitClient.getCountryList(); //This works**
pollCountryList();
}
//Create polling request for every 10 secs
private void pollCountryList(){
final Handler mHandler = new Handler();
new Thread(new Runnable() {
#Override
public void run() {
for (int i=0; i<30;i++){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Call API on main thread
mHandler.post(new Runnable() {
#Override
public void run() {
**myRetrofitClient.getCountryList(); //NOT CALLING ONCHANGED??**
}
});
}
}
}).start();
}
public MutableLiveData<List<CountryModel>> getmCountryList() {
return mCountryList;
}
MyRetrofitClient.getCountryList()
public MutableLiveData<List<CountryModel>> getCountryList(){
final MutableLiveData<List<CountryModel>> lstResult = new MutableLiveData<>();
MockServiceInterface serviceInterface = mRetrofit.create(MockServiceInterface.class);
Call<List<CountryModel>> countryList = serviceInterface.getCountryList();
countryList.enqueue(new Callback<List<CountryModel>>() {
#Override
public void onResponse(Call<List<CountryModel>> call, Response<List<CountryModel>> response) {
if (response.isSuccessful()){
List<CountryModel> lstResponse = response.body();
lstResult.setValue(lstResponse);
}else {
System.out.print(response.errorBody());
}
}
#Override
public void onFailure(Call<List<CountryModel>> call, Throwable t) {
t.printStackTrace();
}
});
return lstResult;
}
Thanks!
EDIT:
Some additional observations-
When i call setValue method of MutableLiveData instance (mCountryList) inside my CountryViewModel it invokes the onChanged method each time.
However its different in case of MyRetrofitClient. The first time setValue is called in MyRetrofitClient.getCountryList(), it invokes the onChanged method. But later it does not.
Sorry, I misunderstood question at first.
You are not receiving changes because you never called setValue on mCountryList.
Method getCountryList() is returning new object MutableLiveData<List<CountryModel>> lstResult everything it is called, to which no one is observing.
Solution:
Instead of returning MutableLiveData object with getCountryList, set mCountryList in onResponse().
Code
public void init(){
mCountryList = new MutableLiveData<>();
myRetrofitClient = new MyRetrofitClient();
myRetrofitClient.getCountryList();
pollCountryList();
}
public LiveData<List<CountryModel>> getCountryListener() {
return mCountryList;
}
public void getCountryList(){
MockServiceInterface serviceInterface = mRetrofit.create(MockServiceInterface.class);
Call<List<CountryModel>> countryList = serviceInterface.getCountryList();
countryList.enqueue(new Callback<List<CountryModel>>() {
#Override
public void onResponse(Call<List<CountryModel>> call, Response<List<CountryModel>> response) {
if (response.isSuccessful()){
List<CountryModel> lstResponse = response.body();
mCountryList.setValue(lstResponse);
}else {
System.out.print(response.errorBody());
}
}
#Override
public void onFailure(Call<List<CountryModel>> call, Throwable t) {
t.printStackTrace();
}
});
}
Use getCountryListener() to observe.
Activity:
countryViewModel.getCountryListener().observe(this,myObserver);

Categories

Resources