Refresh data whit refreshLayout using livedata - android

i have a fragment in my app that i show two list of saparate data in it.i'm using from android architecture components to load my data.
Once the data is fetched from the network, I store it locally using Room DB and then display it on the UI using ViewModel that observes on the LiveData object (this works fine). However, I want to be able to have a refreshLayout which When Refreshing Occurs a refresh action and perform a network request to get new data from the API if and only if there is a network connection.The issue is when Refreshing Occurs data load from locate database and network together .
my question is :How do I manage to get data only from Network when refreshing data?
How do I manage to get data only from Network when refreshing data?
I've seen this question and it didn't help me...
my codes:
repository:
public NetworkResult<LiveData<HomeHealthModel>> getHomeHealth(String query) {
MutableLiveData<String> _liveError = new MutableLiveData<>();
MutableLiveData<HomeHealthModel> data = new MutableLiveData<>();
LiveData<List<GeneralItemModel>> liveClinics = App.getDatabase().getGeneralItemDAO().getTops(GeneralItemType.Clinics, GeneralItemType.TOP);
LiveData<List<GeneralItemModel>> liveDoctors = App.getDatabase().getGeneralItemDAO().getTops(GeneralItemType.Doctors, GeneralItemType.TOP);
setupService(_liveError); //request data from network
data.postValue(new HomeHealthModel(liveClinics, liveDoctors));
_liveError.postValue(String.valueOf(NetworkResponseType.LocaleData));
return new NetworkResult<>(_liveError, data);
}
my viewModel
public class HomeHealthVM extends ViewModel {
private MutableLiveData<String> queryLiveData;
private LiveData<String> networkErrors;
private LiveData<List<GeneralItemModel>> Clinics;
private LiveData<List<GeneralItemModel>> Doctors;
public HomeHealthVM(HealthRepository repository) {
queryLiveData = new MutableLiveData<>();
LiveData<NetworkResult<LiveData<HomeHealthModel>>> repoResult;
repoResult = Transformations.map(queryLiveData, repository::getHomeHealth);
LiveData<HomeHealthModel> model = Transformations.switchMap(repoResult, input -> input.data);
Doctors = Transformations.switchMap(model, HomeHealthModel::getDoctors);
Clinics = Transformations.switchMap(model, HomeHealthModel::getClinics);
networkErrors = Transformations.switchMap(repoResult, input -> input.error);
}
public void search(String queryString) {
queryLiveData.postValue(queryString);
}
public String lastQueryValue() {
return queryLiveData.getValue();
}
public LiveData<String> getNetworkErrors() {
return networkErrors;
}
public LiveData<List<GeneralItemModel>> getClinics() {
return Clinics;
}
public LiveData<List<GeneralItemModel>> getDoctors() {
return Doctors;
}
}
my fragment code:
private void setupViewModel() {
ViewModelFactory<HealthRepository> factory = new ViewModelFactory<>(new HealthRepository());
healthVM = ViewModelProviders.of(this, factory).get(HomeHealthVM.class);
healthVM.getNetworkErrors().observe(this, states -> {
try {
if (Integer.parseInt(states) != WarningDialogType.Success &&
Integer.parseInt(states) != WarningDialogType.Locale) {
stopLoading();
linerNoInternet.setVisibility(View.VISIBLE);
linerContent.setVisibility(View.GONE);
}
} catch (Exception e) {
stopLoading();
linerNoInternet.setVisibility(View.VISIBLE);
linerContent.setVisibility(View.GONE);
}
});
healthVM.getDoctors().observe(this, doctors -> {
if (doctors.size() > 0) {
doctorsAdapter.submitList(doctors);
stopLoading();
} else {
}
});
healthVM.getClinics().observe(this, clinics -> {
if (clinics.size() > 0) {
clinicsAdapter.submitList(clinics);
stopLoading();
} else {
conesClinics.setVisibility(View.GONE);
}
});
healthVM.search("");
}

Related

Combine Two LiveData into one Android

I need to combine these two Data. They both have their own Fragment,Dao, Model and Repository. And both return different data from different tables.
ItemFavourite table stores id of the tables aboves Item and ItemMoto.
public LiveData<Resource<List<Item>>> getItemFavouriteData() {
return itemFavouriteData;
}
//Moto
public LiveData<Resource<List<ItemMoto>>> getItemFavouriteDataMoto() {
return itemFavouriteDataMoto;
}
This is how I tried it.
public class FavouriteViewModel extends PSViewModel {
private final LiveData<Resource<List<Item>>> itemFavouriteData;
private final LiveData<Resource<List<ItemMoto>>> itemFavouriteDataMoto;
private MutableLiveData<FavouriteViewModel.TmpDataHolder> itemFavouriteListObj = new
MutableLiveData<>();
private MutableLiveData<FavouriteMotoViewModel.TmpDataHolder> itemFavouriteListObjMoto = new
MutableLiveData<>();
#Inject
FavouriteViewModel(ItemRepository itemRepository, ItemMotoRepository itemMotoRepository) {
itemFavouriteData = Transformations.switchMap(itemFavouriteListObj, obj -> {
if (obj == null) {
return AbsentLiveData.create();
}
Utils.psLog("itemFavouriteData");
return itemRepository.getFavouriteList(Config.API_KEY, obj.userId, obj.offset);
});
itemFavouriteDataMoto = Transformations.switchMap(itemFavouriteListObjMoto, obj -> {
if (obj == null) {
return AbsentLiveData.create();
}
Utils.psLog("itemFavouriteData");
return itemMotoRepository.getFavouriteList(Config.API_KEY, obj.userId, obj.offset);
});
}
public LiveData<Resource<List<Item>>> getItemFavouriteData() {
return itemFavouriteData;
}
public LiveData<Resource<List<ItemMoto>>> getItemFavouriteDataMoto() {
return itemFavouriteDataMoto;
}
private static LiveData<Resource<List<Item>>> mergeDataSources(LiveData... sources) {
MediatorLiveData<Resource<List<Item>>> mergedSources = new MediatorLiveData();
for (LiveData source : sources) {
mergedSources.addSource(source, mergedSources::setValue);
}
return mergedSources;
}
public LiveData<Resource<List<Item>>> getFavourites() {
return mergeDataSources(
getItemFavouriteDataMoto(),
getItemFavouriteData());
}
}
From Fragment I observe the data like this:
LiveData<Resource<List<Item>>> news = favouriteViewModel.getFavourites();
if (news != null) {
news.observe(this, listResource -> {
if (listResource != null) {
switch (listResource.status) {
case LOADING:
// Loading State
// Data are from Local DB
if (listResource.data != null) {
//fadeIn Animation
fadeIn(binding.get().getRoot());
// Update the data
replaceData(listResource.data);
}
break;
case SUCCESS:
// Success State
// Data are from Server
if (listResource.data != null) {
// Update the data
replaceData(listResource.data);
}
favouriteViewModel.setLoadingState(false);
break;
case ERROR:
// Error State
favouriteViewModel.setLoadingState(false);
favouriteViewModel.forceEndLoading = true;
break;
default:
// Default
break;
}
} else {
// Init Object or Empty Data
if (favouriteViewModel.offset > 1) {
// No more data for this list
// So, Block all future loading
favouriteViewModel.forceEndLoading = true;
}
}
});
}
The only data I am getting are from Item table only.
Using mediator live data we can observe the 2 livedata.
val groupChatFeed: LiveData<List<Feed<*>>> = MediatorLiveData<List<Feed<*>>>().apply {
fun prepareDataAndSetStates(): List<Feed<*>> {
val data: MutableList<Feed<*>> = mutableListOf()
if (postList.value?.data?.isNullOrEmpty() == false) {
data.addAll(postList.value?.data ?: emptyList())
}
if (connectionRecommendations.value?.data?.isNullOrEmpty() == false) {
val recommendations = connectionRecommendations.value?.data?.toFeedItem()
data.add(recommendations)
}
return data
}
addSource(postList) {
value = prepareDataAndSetStates()
}
addSource(connectionRecommendations) {
value = prepareDataAndSetStates()
}
}
We are observing 2 different livedata postList and connectionRecommendations.
You can use MediatorLiveData and tuples, but you can technically also use this library I wrote for this specific purpose which does it for you, and solve it like this
import static com.zhuinden.livedatacombineutiljava.LiveDataCombineUtil.*;
private final LiveData<Pair<Resource<List<Item>>, Resource<List<ItemMoto>>>> favorites = combine(itemFavouriteData, itemFavouriteDataMoto, (favorites, favoriteMoto) -> {
return Pair.create(favorites, favoriteMoto);
});
public LiveData<Pair<Resource<List<Item>>, Resource<List<ItemMoto>>>> getFavorites() {
return favorites;
}

Update password option through rxjava and retrofit does not work

I want to create change password option for my app which will update the current password with new pasword and Im using rxjava and retrofit to send a update request to server. Sorry if Im having issues with the correct terminologies. Im new to android. Issue im having is Validations I have added to viewmodel does not work properly. I think its because of the fragment class not configured properly. im having trouble with setting it to to show error messages(such as "Old Password is required" and "New Password is required") which should be validated by the viewmodel and change password according to that.
Im currently getting a "cannot resolve method maketext" error from the Toast I have made in the fragment class.
Any help with this matter is highly appreciated.Please find my code here. Also please let me know if my approach is correct or how it can be improved.
UpdatePasswordFragment.java
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mViewModel = ViewModelProviders.of(this).get(UpdatePasswordViewModel.class);
binding.setViewModel(mViewModel);
//mViewModel.setUser(new Gson().fromJson(getIntent().getStringExtra(Constants.INTENT_USER), User.class));
mViewModel.setUser(new Gson().fromJson(getArguments().getString("user"), User.class));
binding.setLifecycleOwner(this);
mViewModel.getMessage().observe(this, s -> {
Toast.makeText(this,s, Toast.LENGTH_LONG).show();
});
}
UpdatePassowrdViewModel.java
public class UpdatePasswordViewModel extends ViewModel {
private Repository Repository;
Application application;
public void init(Application application) {
this.application = application;
showSpinner.setValue(false);
Repository = new Repository(application);
updatePasswordMutableLiveData.setValue(new UpdatePassword());
}
private MutableLiveData<UpdatePassword> updatePasswordMutableLiveData = new MutableLiveData<>();
private MutableLiveData<Boolean> showSpinner = new MutableLiveData<>();
private final String SUCCESS_MESSAGE = "Password Successfully Changed";
private User mUser;
public MutableLiveData<String> getOldPassword() {
return oldPassword;
}
public void setOldPassword(MutableLiveData<String> oldPassword) {
this.oldPassword = oldPassword;
}
public MutableLiveData<String> getNewPassword() {
return newPassword;
}
public void setNewPassword(MutableLiveData<String> newPassword) {
this.newPassword = newPassword;
}
public MutableLiveData<String> getConfirmNewPassword() {
return confirmNewPassword;
}
public void setConfirmNewPassword(MutableLiveData<String> confirmNewPassword) {
this.confirmNewPassword = confirmNewPassword;
}
private MutableLiveData<String> oldPassword = new MutableLiveData<>();
private MutableLiveData<String> newPassword = new MutableLiveData<>();
private MutableLiveData<String> confirmNewPassword = new MutableLiveData<>();
private MutableLiveData<Boolean> showLoader = new MutableLiveData<>();
public void setUser(User user) {
this.mUser = user;
}
public MutableLiveData<String> getMessage() {
return message;
}
private MutableLiveData<String> message = new MutableLiveData<>();
public MutableLiveData<Boolean> getShowLoader() {
return showLoader;
}
#SuppressLint("CheckResult")
public void changePassword() {
showSpinner.setValue(true);
Repository.changePassword(mUser.getUserName(), oldPassword.getValue(),newPassword.getValue())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
if(SUCCESS_MESSAGE.equals(s)) {
oldPassword.setValue("");
newPassword.setValue("");
confirmNewPassword.setValue("");
}
showSpinner.setValue(false);
message.setValue(s.toString());
}, throwable -> {
showSpinner.setValue(false);
message.setValue(throwable.getLocalizedMessage());
});
}
public void savePasswordClicked(View view) {
if(oldPassword.getValue().trim().length() == 0) {
message.setValue("Old Password is required");
return;
}
if(newPassword.getValue().trim().length() == 0) {
message.setValue("New Password is required");
return;
}
if(!newPassword.getValue().equals(confirmNewPassword.getValue())) {
message.setValue("New Password and Confirm Password doesn't match");
return;
}
changePassword();
}
Repository.Java
public Observable<ApiResponse<User>> changePassword(String userId, String oldPassword, String newPassword) {
// return mApi.updatePassword(UpdatePassword);
return mApi.updatePassword(userId,oldPassword, newPassword );
}
THis is the retrofit call I have made in the APi
#PUT("user/updatepassword")
Observable<ApiResponse<User>> updatePassword(
#Field("currentPassword") String oldPassword,
#Field("newPassword") String newPassword,
#Field("userId") String userId
);
First of all, you are using not only ViewModel here, but data binding too. First thing you need to do to be able to use data binding is to add to your build.gradle the following:
// enable data binding for app here
android {
...
dataBinding {
enabled = true
}
}
Second mistake is that you are making setters and getters for MutableLiveData, you should change the value of the data by calling .setValue(newValue), the reference of the object should be immutable if you want your observers to be notified of change.
The last thing you need to do is to make sure the required fields are binded correctly in you layout, in your case you need a two-way binding, example:
<CheckBox
android:id="#+id/rememberMeCheckBox"
android:checked="#={viewmodel.rememberMe}"
/>
You can read more about two-way data binding here.

How to fix my repository pattern when offline with mvvm

I am creating very simple android app using mvvm and repository pattern. It fetch data from network (using retrofit2/RxJava2) if app is online and saves to DB (using room) and post to observe. If app is offline, app gets the data from DB and post to observe. From activity app updates the textviews after getting observed from viewmodel class.
Everything is working very fine when app has active internet connection. When internet is not available it does not load data from DB. And that's the problem am facing with no clue.
Activity class
viewModel.loadHomeData();
viewModel.homeDataEntityResult().observe(this, this::updateTextViews);
private void updateTextViews(HomeDataEntity data) {
if (data != null) {
tv1.setText(data.todayDate);
tv2.setText(data.bnDate);
tv3.setText(data.location);
}
}
Viewmodel class
private RamadanRepository repository;
private DisposableObserver<HomeDataEntity> disposableObserver;
private MutableLiveData<HomeDataEntity> homeDataEntityResult = new MutableLiveData<>();
public LiveData<HomeDataEntity> homeDataEntityResult() {
return homeDataEntityResult;
}
public void loadHomeData() {
disposableObserver = new DisposableObserver<HomeDataEntity>() {
#Override
public void onNext(HomeDataEntity homeDataEntity) {
homeDataEntityResult.postValue(homeDataEntity);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
};
repository.getHomeData()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.debounce(400, MILLISECONDS)
.subscribe(disposableObserver);
}
Repository class
public Observable<HomeDataEntity> getHomeData() {
boolean hasConnection = appUtils.isOnline();
Observable<HomeDataEntity> observableFromApi = null;
if (hasConnection) {
observableFromApi = getHomeDataFromApi();
}
Observable<HomeDataEntity> observableFromDb = getHomeDataFromDb();
if (hasConnection)
return Observable.concatArrayEager(observableFromApi, observableFromDb);
else return observableFromDb;
}
private Observable<HomeDataEntity> getHomeDataFromApi() {
return apiService.getDemoHomeData()
.map(HomeDataEntity::copyFromResponse)
.doOnNext(homeDataDao::saveData);
}
private Observable<HomeDataEntity> getHomeDataFromDb() {
return homeDataDao.getHomeData()
.toObservable()
.doOnNext(homeDataEntity -> {
Timber.d("db data %s", homeDataEntity.toString());
});
}
When app is online it also prints the roomDB inserted data after fetching. What actually am missing when app is offline?

android livedata make sequential call

I am using Retrofit, Live data. There is one situation on my project, I have to make sequence of network call. if any one fails it should return error.
At present I have two live data observers to get the work done, which is not good approach so I wanted to know the better approach or sample code to handle such requirement.
Note: I am not using Rxjava.
View code Basic logic
String id = "items/1233"; //ID which has to to be deleted
if (isCustomizedItem) {
viewModel.deleteEvent(id);
} else {
viewModel.createCustomItems();
viewModel.deleteEvent(id);
}
Livedata observers
viewModel.getItemDeleted().observe(this, serverResponse -> {
if (serverResponse.status == Status.SUCCESS) {
Timber.i("Successfully deleted");
}
});
viewModel.itemCreated().observe(this, serverResponse -> {
if (serverResponse.status == Status.SUCCESS) {
Timber.i("new items added");
//Again call delete for specific item
viewModel.deleteEvent(id);
}
});
Viewmodel code
createItems = Transformations.switchMap(eventData, (data) -> {
if (canCreateItems(data)) {
return AbsentLiveData.create();
} else {
return eventItemRepository.createItems();
}
});
deleteItem = Transformations.switchMap(deleteItem, (item) -> {
if (!isValidItem(item)) {
return AbsentLiveData.create();
} else {
return eventItemRepository.deleteItem(item);
}
});
Repo code.
public LiveData<Resource<List<Items>>> createItems() {
return new NetworkBoundResource<List<Items>> (executors) {
#NonNull
#Override
protected LiveData<ApiResponse<List<Items>>> createCall() {
return services.createItems();
}
}.asLiveData();
}
public LiveData<Resource<EmptyResponse>> deleteItem(String id) {
return new NetworkBoundResource<EmptyResponse> (executors) {
#NonNull
#Override
protected LiveData<ApiResponse<EmptyResponse>> createCall() {
return services.deleteItem(id);
}
}.asLiveData();
}
Service interface.
#GET(Constants.API_PATH+"/createitems/")
LiveData<ApiResponse<List<Items>>> createItems();
#GET(Constants.API_PATH+"/delete/{id}")
LiveData<ApiResponse<EmptyResponse>> deleteItem(#Path("id") String id);
I want to call createItems and deleteItem together. How can i achieve this?
Finally I write the solution. I used Mediatorlivedata to observe livedata changes on viewmodel.
Method which is responsible for both network call
public LiveData<Resource<EmptyResponse>> updateEvent(RequestCustomEvent request) {
return new UpdateItineraryRequests<EmptyResponse>(request).asLiveData();
}
and a class which will observe live data changes on viewmodel.
private class UpdateItineraryRequests<RequestType> {
private final MediatorLiveData<Resource<RequestType>> result = new MediatorLiveData<>();
UpdateItineraryRequests(RequestCustomEvent request) {
startExecution(request);
}
void startExecution(RequestCustomEvent request) {
//First check the its custom or not if its custom then directly change.
if (request.isCustom()) {
LiveData<Resource<EmptyResponse>> observable = repo.deleteItem(request.getEventID());
result.addSource(observable, response -> {
result.removeSource(observable);
if (response.status == Status.SUCCESS) {
result.setValue(Resource.success(null));
} else {
result.setValue(Resource.error("unable to delete", null));
}
});
} else {
LiveData<Resource<List<Items>>> itemsObservable = repo.createItems(request.getDataToChange());
result.addSource(itemsObservable, response -> {
result.removeSource(itemsObservable);
LiveData<Resource<EmptyResponse>> observable = repo.deleteItem(request.getEventID());
result.addSource(observable, response -> {
result.removeSource(observable);
if (response.status == Status.SUCCESS) {
//Do rest of network calls
}
}
});
}
}
LiveData<Resource<RequestType>> asLiveData() {
return result;
}
}

Load more on retrofit and rxJava

I'm trying load posts from blog. I use mosby + retrofit + rxjava.
public class PostRepository implements IPostRepository {
private Api api;
private long last_id = 0;
private Single<List<Post>> postList;
public PostRepository(Api api) {
this.api = api;
}
#Override
public Single<List<Post>> getList() {
this.load();
return postList;
}
private void load() {
Single<List<Post>> tmp;
Log.d(Configuration.DEBUG_TAG, "Loading " + last_id);
tmp = api.getPostList(last_id)
.map(posts -> {
ArrayList<Post> postList = new ArrayList<>();
for (PostResponse post : posts) {
if (last_id == 0 || last_id > post.id) {
last_id = post.id;
}
postList.add(new Post(
post.id,
post.thumb,
post.created_at,
post.title
));
}
return postList;
});
if (postList == null) {
postList = tmp;
} else {
postList.mergeWith(tmp);
}
}
#Override
public Single<Post> getDetail(long id) {
return api.getPost(id)
.map(postResponse -> new Post(
postResponse.id,
postResponse.thumb,
postResponse.created_at,
postResponse.title,
postResponse.body
));
}
}
and api
public interface Api {
#GET("posts")
Single<PostListResponse> getPostList(#Query("last_id") long last_id);
#GET("post/{id}")
Single<PostResponse> getPost(#Path("id") long id);
}
First query to website is ok. https://site/posts?last_id=0
But second run function getList does not work.
I always get the same get query with last_id = 0, but line in console write
D/App: Loading 1416
D/App: 1416
D/OkHttp: --> GET https://site/posts?last_id=0 http/1.1
if i write
tmp = api.getPostList(1000)
then i get true query string https://site/posts?last_id=1000
Update
I rewrite code repository.
public class PostRepository implements IPostRepository {
private Api api;
private long last_id = 0;
private List<Post> postList = new ArrayList<>();
private Observable<List<Post>> o;
public PostRepository(Api api) {
this.api = api;
}
#Override
public Single<List<Post>> getList() {
return load();
}
private Single<List<Post>> load() {
return api.getPostList(last_id)
.map(posts -> {
for (PostResponse post : posts) {
if (last_id == 0 || last_id > post.id) {
last_id = post.id;
}
postList.add(new Post(
post.id,
post.thumb,
post.created_at,
post.title
));
}
return postList;
});
}
#Override
public Single<Post> getDetail(long id) {
return api.getPost(id)
.map(postResponse -> new Post(
postResponse.id,
postResponse.thumb,
postResponse.created_at,
postResponse.title,
postResponse.body
));
}
}
It's work
Your problem lies in this code fragment:
if (postList == null) {
postList = tmp;
} else {
postList.mergeWith(tmp); // here
}
Operators on observables are performing immutable operations, which means that it always returns new stream which is a modified version of the previous one. That means, that when you apply mergeWith operator, the result of this is thrown away as you are not storing it anywhere. The most easy to fix this is to replace the old postList variable with the new stream.
However, this is not optimal way of doing this. You should have a look on Subjects and emit new values within the old stream as your current solution will not affect previous subscribers as they have subscribed to a different stream

Categories

Resources