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()
}
Related
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);
}
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();
}
. Get stuck with a basic scenario of loading the data in oncreate of an activity. So I am trying to load the data as soon as i open my activity but when i change the screen orientation it gets called again.
below is my rest client for retrofit
public class MyRestApiClient {
private static Retrofit retrofit = null;
public static Retrofit getClient() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).connectTimeout(30,TimeUnit.SECONDS).readTimeout(30,TimeUnit.SECONDS).build();
Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
retrofit = new Retrofit.Builder()
.baseUrl("http://localhost:8080/rest/")
.addConverterFactory(GsonConverterFactory.create(gson))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(client)
.build();
return retrofit;
}
}
below is my resturl interface for loading the data
public interface MyRestUrlInterface {
#GET("user/{user_id}")
Call<Object> getData(#Path("user_id") String user_id);
}
below is my viewmodel class:
public class MyViewModelObserver extends ViewModel {
private MutableLiveData<Object> httpCallBackObserver;
public MutableLiveData<Object> getHttpCallBackObserver() {
if (httpCallBackObserver == null) {
httpCallBackObserver = new MutableLiveData<Object>();
}
return httpCallBackObserver;
}
}
below is my Activity code :
public class MyActivity extends AppCompatActivity {
private static final String TAG = "MyActivity" ;
MyRestUrlInterface restUrlInterface;
public MyViewModelObserver myViewModelObserver;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
restUrlInterface = MyRestApiClient.getClient().create(MyRestUrlInterface.class);
myViewModelObserver = ViewModelProviders.of(this).get(MyViewModelObserver.class);
myViewModelObserver.getHttpCallBackObserver().observe(this, getData());
//load data via http
Call<Object> call = restUrlInterface.getData("123");
call.enqueue(new Callback<Object>() {
#Override
public void onResponse(Call<Object> call, Response<Object> response) {
myViewModelObserver.getHttpCallBackObserver().setValue(response.body());
}
#Override
public void onFailure(Call<Object> call, Throwable t) {
}
});
}
private Observer<Object> getData(){
return new Observer<Object>() {
#Override
public void onChanged(#Nullable final Object responseString) {
Log.d(TAG,"***** Loaded Data --- "+responseString);
}
};
}
}
How to use view model so that it wont make http call again in screen orientation change
suggested answer:
public class MyViewModelObserver extends ViewModel {
private MutableLiveData<Object> httpCallBackObserver;
public MutableLiveData<Object> getHttpCallBackObserver() {
if (httpCallBackObserver == null) {
httpCallBackObserver = new MutableLiveData<Object>();
loadData();
}
return httpCallBackObserver;
}
private void loadData(){
Call<Object> call = restUrlInterface.getData("123");
call.enqueue(new Callback<Object>() {
#Override
public void onResponse(Call<Object> call, Response<Object> response) {
myViewModelObserver.getHttpCallBackObserver().setValue(response.body());
}
#Override
public void onFailure(Call<Object> call, Throwable t) {
}
});
}
}
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)
I am implementing get request with Retrofit in Mvvm architecture, but call.enqueue is not getting executed. When i try it without mvvm architecture in simple activity it works fine.
Github:
https://github.com/nikolabozicbg/Retrofit
I tried to log onresponse and onfailure but nothing gets logged
Here is my Fragment code:
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
listItemViewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ListGamesViewModel.class);
listItemViewModel.getListItems().observe(this, new Observer<List<ListGames>>() {
#Override
public void onChanged(#Nullable List<ListGames> listItems) {
}
});
}
View model code:
public class ListGamesViewModel extends ViewModel {
private RetrofitRpository repository;
public ListGamesViewModel(RetrofitRpository repository) {
this.repository = repository;
}
public LiveData<List<ListGames>> getListItems() {
System.out.println("iz live model");
return (LiveData<List<ListGames>>) repository.getListOfData();
}
}
Repository code:
public class RetrofitRpository {
List<ListGames> listItemList;
public RetrofitRpository() {
}
public List<ListGames> getListOfData() {
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl("http://mobilews.365scores.com/").addConverterFactory(GsonConverterFactory.create());
Retrofit retrofit = builder.build();
LisgGamesCLient client = retrofit.create(LisgGamesCLient.class);
Call<Example> call = client.reposForUser();
call.enqueue(new Callback<Example>() {
#Override
public void onResponse(Call<Example> call, Response<Example> response) {
System.out.println("onresponse");
}
#Override
public void onFailure(Call<Example> call, Throwable t) {
System.out.println("onfail");
}
});
return listItemList;
}
}
CLient code:
public interface LisgGamesCLient {
#GET("Data/Games/?lang=1&uc=6&tz=15&countries=1")
Call<Example> reposForUser();
}
Example class code: