I want to post each RealmResults data to a REST endpoint, and want to delete its data if sending is success.
Running following code, success sending but fail to delete.
I tried to use target.deleteFromRealm() in Response() but IllegalStateException occurred.
java.lang.IllegalStateException: Realm access from incorrect thread.
Realm objects can only be accessed on the thread they were created.
How can I delete target?
(using Realm Java 3.1.2 and Retrofit 2.2.0)
RealmResults<Model> results = realm.where(Model.class).findAll();
for ( final Model target: results ){
Call<Void> task = restInterface.post(gson.toJson(target));
task.enqueue( new CallBack<Void>(){
#Override
public void onResponse(Call<Void> call, Response<Void> response) {
// ?? how to delete target from realm ??
}
#Override
public void onFailure(Call<Void> call, Throwable t) {
// do nothing
}
});
}
It is the same as removing items for normal ArrayLists. That is not allowed either and will throw ConcurrentModificationException.
Also I would suggest not to send items one by one to server, but collect them into array and pass all the data in one request.
To collect all data into ArrayList you can use
RealmResults<Model> results = realm.where(Model.class).findAll();
ArrayList<Model> list = new ArrayList(results);
And then try to send data like this:
Call<Void> task = restInterface.post(gson.toJson(list));
task.enqueue( new CallBack<Void>(){
#Override
public void onResponse(Call<Void> call, Response<Void> response) {
// As a result all data will be uploaded in the same one batch and you can safely clear the db
results.deleteAllFromRealm();
}
#Override
public void onFailure(Call<Void> call, Throwable t) {
// do nothing
}
});
Related
Let's say I have a User view model to load the user's data. I would use Retrofit2 to load the users data. A simple example would be like this:
call.enqueue(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
if (response.isSuccessful()) {
userMutableLiveData.setValue(response.body());
} else {
//since response.body() is null, how to set a suitable value to userMutableLiveData.setValue()
}
}
#Override
public void onFailure(Call<User> call, Throwable t) {
//since there was a networking failure, how to set a suitable value to userMutableLiveData.setValue()
}
});
Now my question is when retrofit returns response code is not successful or a networking failure occur how do I set a suitable value so that I can give the user a good feedback of what happened.
I'm a beginner with Android and have written a simple retrofit example:
LOGININTERFACE mAPISERVICE;
mAPISERVICE= LOGINGENERATOR.getAPIServices();
mAPISERVICE.savePost("0016642902","0016642902","password").enqueue(new Callback<LGOINMODEL>() {
#Override
public void onResponse(Call<LGOINMODEL> call, Response<LGOINMODEL> response) {
LGOINMODEL output=response.body();
if(response.isSuccessful())
test[0] ="behzad behzad behzad";
}
#Override
public void onFailure(Call<LGOINMODEL> call, Throwable t) {
}
});
But in this line:
test[0] ="behzad behzad behzad";
It can't return a value, and have not read any tutorials, examples, or other posts on Stack Overflow, and so still cannot solve this problem.
you need a callback so whenever you make a call to this function to get the values from the server you can pass the callback and when results are there it can give you back.
Callback interface
MyResultsListener.java
public interface MyResultsListener{
public void onData(LGOINMODEL loginModel);
public void onFailed();
}
now suppose the call you made is in some function then pass the MyResultsListener reference as a parameter to get back the results.
public void makeServerCall(MyResultsListener listener){
LOGININTERFACE mAPISERVICE;
mAPISERVICE= LOGINGENERATOR.getAPIServices();
mAPISERVICE.savePost("0016642902","0016642902","password").enqueue(new Callback<LGOINMODEL>() {
#Override
public void onResponse(Call<LGOINMODEL> call, Response<LGOINMODEL> response) {
LGOINMODEL output=response.body();
if(response.isSuccessful())
test[0] ="behzad behzad behzad";
//for getting back data to calling class or function
listener.onData(output);
}
#Override
public void onFailure(Call<LGOINMODEL> call, Throwable t) {
//for failure handling
listener.onFailed();
}
});
}
With retrofit2 is possible make synchronous call:
mAPISERVICE = LOGINGENERATOR.getAPIServices();
Callback<LOGINMODEL> mLOGINMODEL = mAPISERVICE.savePost("0016642902","0016642902","password");
Response<LOGINMODEL> response = mLOGINMODEL.execute();
...
however, synchronous requests trigger app crashes on Android 4.0 or newer. You’ll run into the NetworkOnMainThreadException error.
More information here.
There's an example provided in NetworkBoundResource, but when I tried it out, if database returns a result, it does not fetch from network anymore. What I want is display data from database and trigger network at the same time and eventually replace data in database after network completes. Example codes will be much appreciated.
I would use room database to save your items in a table. Then you use Paging list to observe to that table. The first time you observe to that table, also do a request to network. When you receive the response delete all items from the table and insert the new ones (all this in a repository class, and in a background thread). This last step will update your paging list automatically as your paging list is observing to that room table.
Guess this article could be helpful:
https://proandroiddev.com/the-missing-google-sample-of-android-architecture-components-guide-c7d6e7306b8f
and the GitHub repo for this article:
https://github.com/PhilippeBoisney/GithubArchitectureComponents
The heart of this solution is UserRepository:
public LiveData<User> getUser(String userLogin) {
refreshUser(userLogin); // try to refresh data if possible from Github Api
return userDao.load(userLogin); // return a LiveData directly from the database.
}
// ---
private void refreshUser(final String userLogin) {
executor.execute(() -> {
// Check if user was fetched recently
boolean userExists = (userDao.hasUser(userLogin, getMaxRefreshTime(new Date())) != null);
// If user have to be updated
if (!userExists) {
webservice.getUser(userLogin).enqueue(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
Toast.makeText(App.context, "Data refreshed from network !", Toast.LENGTH_LONG).show();
executor.execute(() -> {
User user = response.body();
user.setLastRefresh(new Date());
userDao.save(user);
});
}
#Override
public void onFailure(Call<User> call, Throwable t) { }
});
}
});
}
I wish to handle all my responses in single method. the purpose is to recall the service when the response code is not 3, when the response code is 3 I intend to first refresh the token and then recall the same service.
I've created a BaseCallback class to catch one method but I can't see the log and can't catch breakpoints.
BASECALLBACK.class
public class BaseCallBack<T> implements Callback<T> {
#Override
public void onResponse(Call<T> call, Response<T> response) {
if (!response.isSuccessful()){
Log.d("BaseCallback","Response Fail");
}
}
#Override
public void onFailure(Call<T> call, Throwable t) {
t.toString();
}
}
CALL METHOD
ApiManager.getInstance().getUsers().enqueue(new BaseCallBack<List<User>>() {
#Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
if (response.isSuccessful()){
}
}
#Override
public void onFailure(Call<List<User>> call, Throwable t) {
}
});
I just want to handle my services single method.
Your starting point is good - you have an ApiManager which is the single point you're looking for - a class, NOT a method (methods shouldn't be a single contact point in this case, it will make your code unreadable and harder to debug later.
From here it would probably be better to use your own custom interface, and implement it however you wish from where you call the request, there you can handle the stuff you want, this is a very generic example that should fix some stuff and get you going.
Be mindful to the fact that this still requires you to work - tweak and add the stuff you need.
This is all you need as an interface (very basic, you can add stuff)
public interface CustomCallListener<T>
{
public void getResult(T object);
}
This is how you should use it in you ApiManager - it receives your interface as a parameter carrying the expected object type, when the response returns do what you need - parse it, cut it, whatever - and cast it into the correct object, this example uses a String response and a List return object, you can expect whatever you think and parse it accordingly, Retrofit2 allows you to parse JSON strings directly (using GSON or some other library), so it's your decision on what to use here - if you don't know what I mean - read about it.
This is also where I would add breakpoints and Log. calls to debug the response you get as you get it you can also break down rawResponse for headers and other stuff.
class ApiManager
{
// .. other stuff you need...
public void getSomeList(final CustomCallListener<List<SomeObject>> listener)
{
Call<ResponseBody> request = serviceCaller.getSomeInfo(userid);
//...other stuff you might need...
request.enqueue(new Callback<ResponseBody>()
{
#Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> rawResponse)
{
try
{
String response = rawResponse.body().string();
//...other stuff you might need...
//...do something with the response, convert it to
//return the correct object...
SomeObject object = new SomeObject(response);
listener.getResult(object);
}
catch (Exception e)
{
// .. the response was no good...
listener.getResult(null);
}
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable throwable)
{
// .. the response was no good...
listener.getResult(null);
}
});
}
}
Finally this is what you should use from anywhere in your code - this allows you to implement the callback right there and handle anything you need by what you return from the ApiManager.
ApiManager.getInstance().getSomeList(new CustomCallListener<List<SomeObject>>()
{
#Override
public void getResult(List<SomeObject> objects)
{
if (null != objects)
{
// check objects and do whatever...
}
else
{
// ... do other stuff .. handle failure codes etc.
}
}
});
Stuff to notice
As mentioned - this is a very generic skeleton that can be greatly modified (add more methods to the interface with different return types to handle Exceptions, Bad responses, other objects, add more params to the same method to handle more options, etc.) read about the subject more, beware of passing null Objects, use try and catches to avoid crashes.
Hope this Helps!
I can't get the data outside onResponse in Restrofit
this is my code
List<Categorie> categorylist=new ArrayList<>();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
final CategoryApi api = retrofit.create(CategoryApi.class);
Call<List<Categorie>> categoryCall = api.categories();
categoryCall.enqueue(new Callback<List<Categorie>>() {
#Override
public void onResponse(Call<List<Categorie>> call, Response<List<Categorie>> response) {
categorylist = (List<Categorie>)response.body();
Log.i("success","Inside "+categorylist.toString());
// here i get the data
}
#Override
public void onFailure(Call<List<Categorie>> call, Throwable t) {
System.out.println("Erreur");
}
});
Log.i("success","Outside "+categorylist.toString());
// here i get null
i've tried making categorylist volatile and static and it didn't work
It appears you're trying to access categorylist immediately after calling categoryCall.enqueue. enqueue is an asynchronous operation which will not complete before the next line ( containing categorylist.toString()) is executed.
Try calling another method where you've left the comment "here i get the data"
public void onResponse(/*...*/ response) {
categorylist = (List<Categorie>)response.body();
Log.i("success","Inside "+categorylist.toString());
// here i get the data
businessLogic(categorieslist);
}
private void businessLogc(List<Categorie> categories) {
myView.showCategories(categories); // or whatever you're doing with data
}
You know, http request is a process that require time, so it should be done asynchronously. Fortunately retrofit handle everything for us.
What you need to do, is when we get response from OnResponse, you need to update your current view, for example if you're using recyclerview, there is recyclerview.notifyDataSetChanged() method.
If you want to get the result from beginning. I'm suggest you call the enqueue method in other activity or somewhere else, and then when you get the result, save it into sqlite database.
And then when you open CategoryActivity load it from database.
You can add refresh button to call the enqueue again to update the database.