So, I am a newbie Android programmer trying to learn RxJava/RxAndroid.
The App I am developing to study has a local database (using Cupboard) that is backed by an API. It has two screens: one showing a list of items and other the items details. Whenever I need to show the items I call this method to load them from my local database and:
public void getItems() {
List<Item> Items = cupboard().withDatabase(database).query(Item.class).list();
if (Items == null || Items.isEmpty()) {
service.listItems().enqueue(new Callback<Items>() {
#Override
public void onResponse(Call<Items> call, Response<Items> response) {
if (response.isSuccessful()) {
cupboard().withDatabase(database).put(response.body().getItems());
mDataReceiver.onItemsLoaded(response.body().getItems());
} else {
Log.d(ItemsLoader.class.getSimpleName(), response.message());
}
}
#Override
public void onFailure(Call<Items> call, Throwable t) {
Log.d(ItemsLoader.class.getSimpleName(), t.getMessage());
}
});
} else {
mDataReceiver.onItemsLoaded(Items);
}
}
If nothing (empty list or null) is returned, I load them from the API, save it in my local database and returns it to the user.
I was trying to refactor this code into reactive (using RxJava, RxAndroid and RxCupboard) by chaining my database calls with the API call, when need, using flatMap but I just couldn't make it work nor wrap my head around it.
Is it possible to refactor this code into Reactive or should I leave this way? If possible, what would be the right way to do this?
This can be solved with a PublishSubject. You can publish data (and errors!) on this object when onResponse() or onFailure() is called. PublishSubject is also an Observable so you can subscribe to its data.
See docs.
Related
Let's say I have an android app with a single activity that contains a button. When I click the button I'd like to make several requests to a rest API that return JSON response. Then I parse the response to a java object and persist it with Room. For the http requests I implemented a Volley request queue as singleton.
The requests are asynchronous and deliver their responses back to the UI thread. There I let Room persist the objects.
I send my http request like this:
RestService.requestSomeData(context, objectId, new ResponseListener() {
#Override
public void onRestSuccess(String response) {
// parse response JSON
// call the insert method
}
#Override
public void onRestError(int code, String errorMessage) {
// handle error
}
}
Since Room forces you to dispatch the queries to worker threads, I'm using RxJava to handle the task. So, for example my Insert method returns an ArrayList of the IDs of the inserted objects wrapped in a Single<ArrayList<Integer>>. Then I call the Insert method and subscribe to the result like this:
myDisposable = MyDatabase.getInstance().myDao()
.insert(myObject)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(idList -> {
Log.d(TAG, "IDs inserted: " + idList.toString());
}, Throwable::printStackTrace);
However, I want to chain multiple requests to the server and then get notified when all are complete and the DB insertions are ready in order to update the UI (e.g. display confirm message, disable the save button). I read numerous articles but nowhere I could find how to perform this apparently easy task. Basically what I want to achieve is:
// a some sort of container for all the observables I get from the database insertions
private Object aPoolOfObservables;
RestService.requestSomeData(context, objectId, new ResponseListener() {
#Override
public void onRestSuccess(String response) {
// parse response JSON
aPoolOfObservables.add(MyDatabase.getInstance().myDao()
.insert(myObject)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()));
}
}
// repeat this n-times more
...
aPoolOfObservables.subscribe(new Listener() {
#Override
public void onComplete() {
// update UI
}
});
Then perform this request multiple times and add the responses to the collection of Single<> (or Maybe<> or Flowable<>) responses and subscribe not to every single stream but to the collection, because I only care that all the operations are complete. Chaining them by firing a request in the onRestSuccess of the previous one seems like a pretty awful solution.
Do you know if there is a RxJava mechanism that allows this?
Is there any general approach/design pattern to handle this situation? I can think of numerous cases when you'd like to e.g. enable a button only after multiple requests have been performed and delivered results. How do you create and subscribe to such event in the context of RxJava? I haven't worked a lot with reactive data so any knowledge will be appreciated.
You can wrap each request in a Single<Pair<ArrayList<Integer>, String>> to store each JSON responses per request. Then, execute them all together with Single.zip(...)
private CompositeDisposable disposables;
private ArrayList<Single<Pair<ArrayList<Integer>, String>>> singles;
RestService.requestSomeData(context, objectId, new ResponseListener() {
#Override
public void onRestSuccess(String response) {
// parse response JSON
// kotlin syntax
singles.add(
MyDatabase.getInstance().myDao().insert(myObject)
.flatMap { ids: ArrayList<String> ->
// transform single to include JSON response
return#flatMap Single.just(Pair(ids, response))
}
);
}
}
// kotlin syntax
disposables.add(
// execute all singles
Single.zip(singles) {}.subscribe()
);
I am trying to build a Rest client with Retrofit 2. This is the first time using this library.
Most of the examples that I have seen normally use a callback function in Android activity or view.
http://www.vogella.com/tutorials/Retrofit/article.html
https://github.com/MeetMe/TwitchTvClient/tree/master/src/com/wdonahue/twitchtvclient
I was wondering if, in the code below, one could return an object of type Appver instead of void?
Thank you!
public void GetAppver()
{
Call<Appver> call = endPoint.GetAppver();
call.enqueue(new Callback<Appver>()
{
#Override
public void onResponse(Call<Appver> call, Response<Appver> response)
{
if (response.isSuccessful())
{
Appver appver = response.body();
}
}
#Override
public void onFailure(Call<Appver> call, Throwable t) { }
});
}
I guess you want to have Appver because you need to use this object to possibly poulate the UI for instance inside a RecyclerView, in this case I guess you cannot avoid to not use void, because you are extending the library. But for sure you can use Dagger2 or a Singleton although there are some contraindications to maintain an instance of Appver so that you can use it in an Adapter for instance
Also in real life is rarely used Retrofit2 in this way, usually you implement it with RXJava2 or with Google Architecture components as ViewModel,LiveData and LifeCycle
You can call Retrofit service methods synchronously.
public AppVer GetAppVer() {
Call<AppVer> call = endPoint.GetAppver();
Response<AppVer> response;
try {
response = call.execute();
} catch (IOException e) {
// Handle network communication errors here
}
if (!response.isSuccessful()) {
// Handle REST service errors here
}
return response.body();
}
However, as you know, you can't do network communication on the main thread in Android. You have to wrap the call in e.g. a Thread, AsyncTask or Service.
Have a look at the following links how to structure your app.
Android Architecture Blueprints: https://github.com/googlesamples/android-architecture
Android Architecture Components: https://developer.android.com/topic/libraries/architecture
I have phone contact numbers list stored in an array and called contactsString[]
and in an online database registered users numbers
I want to count how many registered users are there
and there is my code
for (i=0;i<contactsString.length-1;i++){
Phone phone=new Phone();
phone.phone=contactsString[i]
WebService.getInstance().getApi().checkNumber(phone).enqueue(new Callback<MainResponse>() {
#Override
public void onResponse(Call<MainResponse> call, Response<MainResponse> response) {
if (response.body().status==1){
availableUsers++;
}
}
#Override
public void onFailure(Call<MainResponse> call, Throwable t) {
}
});
}
my problem is the web service response is delayed so it don't count and availableUsers is printed it's initial value which is 0
I would try better sending an array of Phone objects. In this way you would get the correct answer in 1 call.
I would never do this in the way you implemented: imagine you have 500 contacts: you will be doing 500 calls to your server. Now imagine you have 100000 users with 500 contacts each
Try to customize your api call in this format. Which uses async task class.
private void phoneContact() {
new AsyncTask<String,Void,String>() {
#Override
protected String doInBackground(String ... params) {
try {
Platform http = Url_Contacts;
JSONObject resp = http.search(what,where);
Log.d(TAG, "Response: " + resp.toString());
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
return "";
}
}.execute();
}
Make sure that your service works well and the format of json with value status in there.
In onResponse, run on UIThread to update your View with the availableUsers.
The enqueue method is asynchronous. So your code should respect the multithreaded nature of it.
There are many approaches you can take:
Replace enqueue() method with execute(). But that makes all the calls synchronous. If you call it in UI Thread then whole app can stutter. Probably you will get NetworkOnMainThreadException. Not a good approach anyway.
Use RxAndroid or RxJava with Observer pattern.
Simple solution. Create a variable int callsFinished = 0;. In onResponse increment that variable. Then if that callsFinished == contactsString.length that means all calls have been done.
In your activity add a listener
void onAllCallsFinished(int availableUsers) {
//do what you want with availableUsers information
}
Call onAllCallsFinished(availableUsers) when callsFinished == contactsString.length.
There you can do what you want with that data. Update a view, call another service.
I am making an Android application and have decided to use Azure's DB platform. I find adding entries to different tables very easy, but querying the DB to be almost impossible.
Currently I am trying to follow this model: How to get the result of OnPostExecute() to main activity because AsyncTask is a separate class?
In short, it uses an interface to implement a AsyncTask with my query executing in doInBackground(). My problem with this is that it is many many lines of code to execute a single simple query, AND I will need to be doing multiple unique queries when running my app and creating tens of separate interfaces/classes seems extremely inefficient.
At the end of the day all I want is to get the results from this query in a managable way:
final MobileServiceList<Users> result = mUser.where().field("username").eq(username).execute().get();
However it seems to not execute without being wrapped in a AsyncTask like this:
new AsyncTask<Users, Void, Users>() {
#Override
protected Users doInBackground(Users... params) {
try {
final MobileServiceList<Users> result = mUser.where().field("username").eq(username).execute().get();
if(result.size() > 0) {
System.out.println("something in list");
return result.get(0);
}
} catch (Exception exception) {
exception.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Users result) {
System.out.println("in post");
if(username.equals((result.username)) && password.equals(result.password)) {
System.out.println("found user");
}
}
}.execute();
Is there any easy way to get around this? All I want is a result from a simple query, I don't understand how this is so hard..
Thanks!
Unfortunately, there is not any simple way to get around this, please refer to AsyncTask reference to know it for helping Android UI thread to get result asynchronously.
I was recently reading about design patterns and especially about low coupling and delegation.
I was wondering, whether there should be any logic on the Activity class or if it only serves the view.
E.g. I have an activity called BattleActivity and that is supposed to work as some kind of session between two players. A lot of Push Notifications happen there, also the class works as an Observer, so there is a lot of comminication going on there.
Right now I am trying to figure out what logic could I move to a separated object(and whether I should) and then just work with the activity.
Example of one of my methods on the activity:
private void postCastedSpell(final int spellId) {
Call call = StaticGlobalContainer.api.postSpellToBattle(Integer.parseInt(battleId), Integer.parseInt(MainActivity.CURRENT_USER_ID), spellId, 100);
call.enqueue(new Callback<User>() {
#Override
public void onResponse(Response<User> response, Retrofit retrofit) {
User user = response.body();
if (response.code() == 202) {
// 200
Log.i("Posting spell to battle", "Success");
Boolean affectedUserIsOpponent = isUserOpponent(user);
if (affectedUserIsOpponent && user.currentHp<1){
StaticGlobalContainer.battleOnResult(Constants.WON, getApplicationContext());
}else {
updateBattleLog(affectedUserIsOpponent, user, spellId);
}
// TODO: do something here
} else {
// 404 or the response cannot be converted to User.
Log.e("Posting spell to battle", "Error:" + response.errorBody());
}
}
#Override
public void onFailure(Throwable t) {
Log.i("HttpRequest-Post spell", "Failure");
}
});
}
It's not specifically bad to put a lot of logic in Activities, but you're right to try to keep it only view related things. If the app is relatively small, it might not be worth moving the logic out. Also, there is some overhead to using abstractions.
if your abstractions aren't supplying a significant benefit, you should avoid them
I try to keep any big data objects in a manager class, so given your example, it might worthwhile to create a Battle manager class to hold all the logic involved in it, like this postCastedSpell function. This way all the Battle information is self contained, and also can be used elsewhere in other activities.
Just keep in mind if you're use data manager classes and you want them to prompt some sort of interation with the UI, you'll have to use Callbacks or the Bus pattern since the Battle manager won't have access to your UI. For example, to call the postCastedSpell the call would look like:
BattleActivity:
BattleManager bm = BattleManager.getInstance(user1, user2);
onSpellClicked() {
bm.castSpell(spellId, user1, callback)
}
BasicCallback callback = new BasicCallback() {
#Override
onComplete() {
if (MyInfoFragment.this.isVisible()) {
[Update UI]
}
}
};
NOTE: When using callbacks like my example, when it finally gets called the activity may have already gone out of view and have been already garbage collected. So in the callback function you need to first make sure it is still visible before trying to modify the UI that possibly no longer exists.