I need when refresh page make request to API and insert getting data to my room database. But when I try to insert data I get io.reactivex.exceptions.OnErrorNotImplementedException: UNIQUE constraint failed: data.id (code 1555). So I decided to check is my table empty and if isn't make update request, but so my recyclerview doesn't work normally, and data doesn't update properly on db. Here is my code:
private void getData() {
progressBar.setVisibility(View.VISIBLE);
APIInterface service = RetrofitInstance.getRetrofitInstance().create(APIInterface.class);
Call<DataAll> call = service.getData();
call.enqueue(new Callback<DataAll>() {
#Override
public void onResponse(Call<DataAll> call, Response<DataAll> response) {
if(response.body() != null) {
List<Data> allData = response.body().getData();
Disposable disposable = db.dataDao().dataSize().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<Integer>() {
#Override
public void accept(Integer dbSize) throws Exception {
if(dbSize > 0)
updateData(allData);
else
insertData(allData);
fillListFromDb();
}
});
}
}
#Override
public void onFailure(Call<DataAll> call, Throwable t) {
tvErrorMessage.setVisibility(View.VISIBLE);
recyclerDataList.setVisibility(View.GONE);
progressBar.setVisibility(View.GONE);
swipeRefreshToUpdateList.setRefreshing(false);
Toast.makeText(getApplicationContext(), R.string.errorMessage, Toast.LENGTH_LONG).show();
t.printStackTrace();
}
});
}
private void fillListFromDb() {
Disposable disposable = db.dataDao().getAllData().subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Consumer<List<Data>>() {
#Override
public void accept(List<Data> data) throws Exception {
listData.clear();
listData.addAll(data);
adapter = new MyAdapter(listData);
adapter.notifyDataSetChanged();
recyclerDataList.setAdapter(adapter);
progressBar.setVisibility(View.GONE);
swipeRefreshToUpdateList.setRefreshing(false);
}
});
}
private void updateData(List<Data> data) {
Completable.fromAction( () ->
db.dataDao().updateData(data))
.subscribeOn(Schedulers.io())
.subscribe();
}
private void insertData(List<Data> data) {
Completable.fromAction(() ->
db.dataDao().addData(data)).
subscribeOn(Schedulers.io())
.subscribe();
}
And onCreate method:
#Override
protected void onCreate(Bundle savedInstanceState) {
// ……
swipeRefreshToUpdateList.setOnRefreshListener(() -> {
tvErrorMessage.setVisibility(View.GONE);
recyclerDataList.setVisibility(View.VISIBLE);
getData();
});
}
Please help me
If you want an insert operation to overwrite existing object in DB, then you should use OnConflictStrategy.REPLACE.
See example:
#Dao
interface CatDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertCats(cats: List<Cat>)
You have to check you List<Data> one by one in your updateData
For exemple :
#Override
public void onResponse(Call<DataAll> call, Response<DataAll> response) {
if(response.body() != null) {
List<Data> allData = response.body().getData();
for (Data data:allData){
if(isStored(data.getId())){
updateData(data);
else {
insertData(data);
}
}
}
}
}
}
you can create a new method in your DataDao findDataById(string id) and use it the boolean isStored(String id) method and update it directly
Good luck ! hope that will help you.
Related
I send comments to server with two devices at same time. Later I add them to RecyclerView list. But sometimes one of the comments isn't shown. Code:
private void makeComment(String comment) {
Call<Model> call = restApiClass.commentPost(user_id, post_id, comment, date);
call.enqueue(new Callback<Model>() {
#Override
public void onResponse(Call<Model> call, Response<Model> response) {
if (response.body().isSuccess()) {
//send a comment and later refresh the RecylerView to get new added comment.
getComments();
}
}
private void getComments() {
Call<List<CommentsModel>> call = restApiClass.getCommentsPost(post_id);
call.enqueue(new Callback<List<CommentsModel>>() {
#Override
public void onResponse(Call<List<CommentsModel>> call, Response<List<CommentsModel>> response) {
if (response.isSuccessful()) {
list = response.body();
if (commentsAdapter == null) {
commentsAdapter = new CommentsAdapter(list, getContext());
commentsRecyclerView.setAdapter(commentsAdapter );
}
//if there are already comments in Adapter don't create again
else {
commentsAdapter.addComment(list.get(list.size() - 1));
//go to last index of RecyclerView:
((LinearLayoutManager) commentsPostRecyclerView.getLayoutManager()).
scrollToPositionWithOffset(list.size() - 1, 0);
}
}
addComment() in CommentsAdapter:
public void addComment(CommentsModel comment) {
this.list.add(comment);
notifyItemInserted(this.list.size()-1); //item is added to last position.
}
I'm new to Android development and i am trying to understand Live Data with MVVM architecture.
I am trying to make the main activity recognize when there is a change in an object that belong to the view-model of the activity.
I have created a simple login activity that takes the text from the username and password fields and passes them to the view-model's login function, then the function sends the data to the users repository and then it makes a POST request toa spring-server that is running on my PC.
The repository login function returns a MutableLiveData object with the logged-in username if the username and password are right, and null as it's value otherwise.
The repository works fine( the data coming back from the server is correct). The view-model has a field of type MutableLiveData and it is need to be updated after the login function is called. In the activity there is an observer that supposed to be notified when a changed accrued in the loggedInUser field (of type MutableLiveData) field and even though there is a change, the function onChange of the observer is never activated.
There is some code that i hope will help me explain better.
Main activity:
public class MainActivity extends AppCompatActivity {
public EditText usernameTxt;
public EditText passwordTxt;
public Button loginBtn;
public String loggedInuUser;
LoginViewModel loginViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
usernameTxt = findViewById(R.id.usernameTxt);
passwordTxt = findViewById(R.id.passwordTxt);
loginBtn = findViewById(R.id.loginBtn);
loginViewModel = ViewModelProviders.of(this ).get(LoginViewModel.class);
loginViewModel.init();
try {
loginViewModel.getLoggedInUser().observe(this, new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
Toast toast= Toast.makeText(MainActivity.this,"changed" , Toast.LENGTH_LONG );
toast.show();
}
}
);
}catch (Exception e){
System.out.println("==========================================================");
System.out.println( e.getMessage());
System.out.println("==========================================================");
}
}
protected void onLogInCliked(View v ){
// Toast toast= Toast.makeText(getApplicationContext(),loggedInuUser, Toast.LENGTH_LONG );
// toast.show();
loginViewModel.login(usernameTxt.getText().toString(),passwordTxt.getText().toString());
// Toast toast2= Toast.makeText(getApplicationContext(),loggedInuUser, Toast.LENGTH_LONG );
// toast2.show();
}
}
view-model:
public class LoginViewModel extends ViewModel {
private UsersRepository usersRepository;
private MutableLiveData<String> loggedInUser;
public void init(){
if(loggedInUser!= null){
return;
}
usersRepository = UsersRepository.getInstance();
loggedInUser=new MutableLiveData<>();
}
public MutableLiveData<String> getLoggedInUser(){
return loggedInUser;
}
public void login(String userName , String hashedPassword) {
loggedInUser = usersRepository.login(userName, hashedPassword);
}
}
repository:
public class UsersRepository {
private static UsersRepository usersRepository;
public static UsersRepository getInstance(){
if (usersRepository == null){
usersRepository = new UsersRepository();
}
return usersRepository;
}
private UsersRepositoryApi usersRepositoryApi;
public UsersRepository(){
usersRepositoryApi = RetrofitService.cteateService(UsersRepositoryApi.class);
}
public MutableLiveData<String> login(String username , String hashedPassword){
final MutableLiveData<String> loggedInUser = new MutableLiveData<>();
User user = new User(username,hashedPassword);
usersRepositoryApi.login(user).enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
if (response.isSuccessful()) {
loggedInUser.setValue(response.body());
}
}
#Override
public void onFailure(Call<String> call, Throwable t) {
loggedInUser.setValue(null);
}
});
return loggedInUser;
}
}
In the mainActivity i set the observer and i expect the app to show my the Toast message but nothing happens.
i have tried to see what happens in the view-model and it is a little strange,
so i printed stuff like this:
public void login(String userName , String hashedPassword) {
System.out.println("222======================================");
System.out.println("==========================================");
System.out.println("==========================================");
System.out.println(loggedInUser.getValue());
System.out.println("==========================================");
System.out.println("==========================================");
System.out.println("==========================================");
loggedInUser = usersRepository.login(userName, hashedPassword);
System.out.println("333======================================");
System.out.println("==========================================");
System.out.println("==========================================");
System.out.println(loggedInUser.getValue());
System.out.println("==========================================");
System.out.println("==========================================");
System.out.println("==========================================");
}
in first time that i run the login function the output of both 222 and 333 was null, but in the second time i run the login function the output of 222 was the loggedInUser and the output of 333 was null
in both cases the on change function of the observer was unvisited
does anyone have any idea of what i am doing wrong??
thank you
ronen!
here your problem is with repository code inside repository you are creating new object of mutable live data and observing different one.
Interface Callback{
onSuccess(String response)
onError(String error)
}
public void login(String username , String hashedPassword,Callback callback){
final MutableLiveData<String> loggedInUser = new MutableLiveData<>();
User user = new User(username,hashedPassword);
usersRepositoryApi.login(user).enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
if (response.isSuccessful()) {
callback.onSuccess(response.body());
}
}
#Override
public void onFailure(Call<String> call, Throwable t) {
callback.onError(null);
}
});
}
//login method of your viewmodel
public void login(String userName , String hashedPassword) {
usersRepository.login(userName, hashedPassword,new Callback(){
void onSuccess(String responsebody){
loggedInUser.setValue(responsebody);
}
void onError(String error){
loggedInUser.setValue(responsebody);
}
});
}
In your repository, try changing this part:
loggedInUser.setValue(response.body());
to postValue function. like that:
loggedInUser.postValue(response.body());
Solution by OP.
As Neha Rathore suggested this is the solution that works for me:
in the view Model:
public void login(String userName , String hashedPassword) {
usersRepository.login(userName, hashedPassword, new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
loggedInUser.setValue(response.body());
}
#Override
public void onFailure(Call<String> call, Throwable t) {
loggedInUser.setValue(null);
}
});
}
and in the Repository:
public void login(String username, String hashedPassword,#Nullable final Callback<String> callback){
final MutableLiveData<String> loggedInUser = new MutableLiveData<>();
User user = new User(username,hashedPassword);
usersRepositoryApi.login(user).enqueue(new Callback<String>() {
#Override
public void onResponse(Call<String> call, Response<String> response) {
if (response.isSuccessful()) {
callback.onResponse(call,response);
}
}
#Override
public void onFailure(Call<String> call, Throwable t) {
callback.onFailure(call,t);
}
});
}
I am trying to make webservie call using retrofit and rxjava 2. i was exploring two different approach to use RxJava2. problem is i am getting response whene i use Observable but it is not working with Flowable. Logs are not getting printed when using Flowable i tried to debug it but its not going inside onNext or onComplete or onError. only onSubscribe gets executed.
1) using observable as return type
new WebRequestManager().getContactObservable(userRequest)
.subscribe(new Observer<ResponseData>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(ResponseData responseData) {
Log.e(TAG , "data "+responseData.getStatus());
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
Log.e(TAG , "data complete");
}
}
);
2) Using flowable as return type
new WebRequestManager().getContactFlowable(userRequest)
.subscribe(new Subscriber<ResponseData>() {
#Override
public void onSubscribe(Subscription s) {
Log.e(TAG , "contact subscription ");
}
#Override
public void onNext(ResponseData responses) {
Log.e(TAG , "contact onNext ");
}
#Override
public void onError(Throwable t) {
}
#Override
public void onComplete() {
Log.e(TAG , "contact onComplete ");
}
});
Rest contact retrofit api
public interface ContactApi {
#POST(WebRequest.GET_CONTACTS)
Flowable<ResponseData> getContactFlowable(#Body UserRequest userRequest);
#POST(WebRequest.GET_CONTACTS)
Observable<ResponseData> getContactObservable(#Body UserRequest userRequest);
}
call to webservice
public Flowable<ResponseData> getContactsData(UserRequest userRequest){
return webRequest.getWebClient().create(ContactApi.class).getContacts(userRequest);
}
public Observable<ResponseData> getContact(UserRequest userRequest){
return webRequest.getWebClient().create(ContactApi.class).getContact(userRequest);
}
getting retrofit instance
public static Retrofit getWebClient(){
//if(okHttpClient == null)
okHttpClient = new OkHttpClient.Builder()
.connectTimeout(120,TimeUnit.SECONDS)
.readTimeout(120,TimeUnit.SECONDS)
.writeTimeout(120,TimeUnit.SECONDS)
.addInterceptor(new WebRequestInterceptor("\"application/json\""))
.build();
// if(client == null)
client = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(LoganSquareConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
return client;
}
With Subscribers, you have to call request to get items:
new WebRequestManager().getContactFlowable(userRequest)
.subscribe(new Subscriber<ResponseData>() {
#Override
public void onSubscribe(Subscription s) {
Log.e(TAG , "contact subscription ");
s.request(Long.MAX_VALUE); // <---------------------------------
}
#Override
public void onNext(ResponseData responses) {
Log.e(TAG , "contact onNext ");
}
#Override
public void onError(Throwable t) {
}
#Override
public void onComplete() {
Log.e(TAG , "contact onComplete ");
}
});
See also DisposableSubscriber with its example.
I'm using rx libraries im my app to call some REST api on my server and to show the results on screen.
I'm also following the MVP design pattern. So I have a Presenter and an Interactor classes.
In MainInteractor.java I have the following method:
public Observable<Card> fetchCard(final String clientId, final CardFetchedListener listener) {
Log.i(TAG, "FetchCard method");
// Manipulate the observer
return CARDS
.doOnCompleted(new Action0() {
#Override
public void call() {
Log.d(TAG, "CARDS Completed");
}
})
.flatMap(new Func1<Card, Observable<Card>>() {
#Override
public Observable<Card> call(final Card card) {
return ResourceClient.getInstance(card)
.getIDCard()
.observeOn(AndroidSchedulers.mainThread())
.doOnError(new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
Log.w(TAG, "interactor -> fetchCard 2", throwable);
}
}
})
.flatMap(new Func1<CardMeta, Observable<Card>>() {
#Override
public Observable<Card> call(CardMeta cardMeta) {
card.setCardMeta(cardMeta);
saveOrUpdateCardToTheDb(card);
return Observable.just(card);
}
})
.doOnCompleted(new Action0() {
#Override
public void call() {
Log.d(TAG, "Completed body");
}
});
}
});
}
In the logs I can see the "Completed Body" string.
The above method is being called by MainPresenter.java class as follows:
interactor.fetchCard(clientId, this)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Card>() {
#Override
public void onCompleted() {
Log.i(TAG, "fetchCard onCompleted");
view.hideProgressDialog();
view.updateCardsAdapter(cards);
}
#Override
public void onError(Throwable e) {
Log.e(TAG, "Fetch Card error ", e);
onFailure(parseThrowable(e));
}
#Override
public void onNext(Card card) {
if (card != null) {
Log.i(TAG, card.getTenant() + " was fetched and will be displayed");
}
}
});
The problem is that the onCompleted method in the Presenter class is never bein called. I have tried to call onCompleted myself and it worked, but the problem is I don't know actually when the observable has finished emitting cards.
What am I doing wrong here?
UPDATE
CARDS is also an observable that contains meta info. It is initialized using
Observable.from(tenants)
.filter(...).flatMap(// I'm using create operator here and it is calling its onCompleted method successflly);
i want to use the cached data in realm then update the data from server using retrofit. i managed that by the following:
public void getNotifications() {
Observable.concat(getCashedNotifications(), downloadNotification())
.subscribe(new Action1<List<Notification>>() {
#Override
public void call(List<Notification> notifications) {
setSize(notifications.size() + "");
}
});
}
private Observable<List<Notification>> getCashedNotifications() {
return Observable.just(mRealm.copyFromRealm(mRealm.where(Notification.class).findAll()));
}
private Observable<List<Notification>> downloadNotification() {
return mApiHandler.createRetrofitService(NotificationServices.class)
.getNotificationByUser(10)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(new Action1<NotificationResponse>() {
#Override
public void call(final NotificationResponse notificationResponse) {
setLoading(false);
mRealm.executeTransactionAsync(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
realm.copyToRealmOrUpdate(notificationResponse.getResult().getData().getNotifications());
}
});
}
})
.map(new Func1<NotificationResponse, List<Notification>>() {
#Override
public List<Notification> call(NotificationResponse notificationResponse) {
if (notificationResponse.getResult() != null) {
return notificationResponse.getResult().getData().getNotifications();
} else {
return new ArrayList<>();
}
}
});
}
my problem is to get the current status like :
1- if there is no data in realm show progress
2- if there is no data and no network show error dialog
3- if there is data in realm and no network show the data from realm only
4- if there is no data in realm and no data from retrofit show no data state
any idea how to know the resuslts from concat are from ? (retrofit or realm)
what i ended up with is to edit the getNotifications method to the following
public void getNotifications() {
setNoData(false);
setLoading(false);
if (ConectivityUtils.isDeviceConnectedToNetwork(mContext)) {
if (mRealm.where(Notification.class).count() > 0) {
Observable.concat(getCashedNotifications(), downloadNotification())
.subscribe(new Action1<List<Notification>>() {
#Override
public void call(List<Notification> notifications) {
setSize(notifications.size() + "");
}
});
} else {
// show progress
setLoading(true);
downloadNotification().subscribe(new Action1<List<Notification>>() {
#Override
public void call(List<Notification> notifications) {
setLoading(false);
if (notifications.size() > 0) {
setSize(notifications.size() + "");
} else {
// no data in realm and retrofit
setNoData(true);
setErrorMessage("No data");
}
}
});
}
} else {
if (mRealm.where(Notification.class).count() > 0) {
getCashedNotifications().subscribe(new Action1<List<Notification>>() {
#Override
public void call(List<Notification> notifications) {
setSize(notifications.size() + "");
}
});
} else {
//show no network
setNoData(true);
setErrorMessage("No Network");
}
}
}
but i believe that there is better and cleaner solution than this