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.
Related
I want to implement Google Play Games Services in my game on the libgdx engine. I tried using gdx-gamesvcs for this. But I am having trouble saving data. I understood from the example that one value is being saved, not the entire state of the game. So I decided to check it out: save and load one value using gsClient.loadGameState and gsClient.saveGameState. I deliberately deleted the game data from the device. But as a result, not only the test value changed, but many others as well. I thought that the state of the entire game is being saved, but the values obtained do not fit into the logic of the game and could not be obtained in it.
How should I use this tool and is it worth it at all, or is it better to use what libgdx itself offers?
Here is a piece of code:
if (gsClient.isSessionActive()) {
try {
gsClient.saveGameState("data", intToByteArray(testValue), 0, null);
} catch (UnsupportedOperationException unsupportedOperationException) {
}
if (gsClient.isSessionActive()) {
try {
gsClient.loadGameState("data", new ILoadGameStateResponseListener() {
#Override
public void gsGameStateLoaded(byte[] gameState) {
if (gameState != null) {
setTestValue(bytesToInt(gameState));
}
}
});
} catch (UnsupportedOperationException unsupportedOperationException) {
}
}
UPD
Yes, saving occurs both to the cloud and to the device, for saving to the device I use Preferences. I have a Google account login button in the game, it works, I have repeatedly seen this standard bar of my account level, which appears at the top when I log in. Everything is set up in the developer console too, I have an id for achievements and leaderboards. In code, I work with the client like this (In the create() method):
public IGameServiceClient gsClient;
if (gsClient == null) {
gsClient = new MockGameServiceClient(1) {
#Override
protected Array<ILeaderBoardEntry> getLeaderboardEntries() {
return null;
}
#Override
protected Array<String> getGameStates() {
return null;
}
#Override
protected byte[] getGameState() {
return new byte[0];
}
#Override
protected Array<IAchievement> getAchievements() {
return null;
}
#Override
protected String getPlayerName() {
return null;
}
};
}
gsClient.setListener(this);
gsClient.resumeSession();
Next is loading.
The exception is not caught, I removed it and everything works as before.
Well, libgdx offers no built-in cloud-save, it is hard to use it for that. :-)
You should in any case save to local AND to cloud, as the cloud is not very fast to load its state.
I can see no problem in your code besides the fact that you swallow an UnsupportedOperationException that is thrown if you did not activate cloud save feature. So the interesting question is: what happens if you don't swallow the exception, and did you intialize GpgsClient with cloud save enabled? Are you really logged in to Gpgs, and is the feature also activated in your developer console?
The main problem was that gameState was null, this arose due to the fact that you had to wait 24 hours after enabling the save function in the developer console, and the advice on clearing the memory of google play games on the test device did not help. After a while gameState began to pass the existing values, but I started having problems with the graphics flow, probably due to the asynchronous loading.
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.
In an Android app that I'm writing, much of the app involves performing web requests to a specific API. Since the format of the data ends up being the same, I wanted to centralize many of the functions that I end up performing every request, rather than rewriting them every time.
For example, I perform the same error checking routine every time I make a web request:
JSONObject jo = new JSONObject(response);
boolean success = jo.getBoolean("success");
if(success) {
//Do work
} else {
//Display error
}
My thought was to make this some sort of class that implements Response.Listener, but I couldn't determine an effective way of handling errors and such. My question is is there an easy way to perform repeated functions in callbacks? I'm looking for an elegant solution, not necessarily the easiest.
You can achieve this by passing one or two interfaces to your method which is cumbersome. Java 8 brings you lambda which makes working with callback much more elegant. To use lambdas in Android you can use retrolambda: https://github.com/orfjackal/retrolambda
Your generic method could look like this
parseResponse(String response,
Consumer<JSONObject> successConsumer,
Consumer<String> errorConsumer) {
JSONObject jo = new JSONObject(response);
boolean success = jo.getBoolean("success");
if(success) {
successConsumer.accept(jo);
} else {
errorConsumer.accept("error");
}
}
You would use this method like this:
class MyClass {
void onResponse(String response) {
....
parseResponse(response, this::handleData, this::handleError);
}
void handleData(JSONObject object) {....}
void handleError(String object) {....}
}
We are using RealChangeListener to listen data changes and updating listview by calling notifydatasetgchanged().
Initial sync time we do get many records from server(per batch 100 records), loop thru results updating Realm like below in background thread.
for(int i=0;i<results.size();i++)
{
// processing and validation
....
db.beginTransaction();
db.copyToRealm(processedObject);
db.commitTransaction();
}
In activity, we are registered realmResults change listener like below code
#Override
public void onStart() {
super.onStart();
mEntityDataProvider = new EntityDataProvider();
mEntityDataProvider = mEntityDataProvider.getListAsync();
mEntityDataProvider.addChangeListener(realmEntityChangeListener);
}
private RealmChangeListener<RealmResults<Entity>> realmEntityChangeListener = new RealmChangeListener<RealmResults<Entity>>() {
#Override
public void onChange(RealmResults<Entity> realmResults) {
if (mEntityListAdapter!= null) {
mEntityListAdapter.setData(realmResults);
mEntityListAdapter.notifyDataSetChanged();
}
}
};
Questions:
Is it best practice to call notifyDataSetChanged() in
reamlChangeListner?
For every commitTransaction() i think
realmChangeListener will be called, calling notifyDataSetChanged() calling many times is it fine?
If above practice is not good to do,
suggest me if any alternatives I need to consider.
Thanks
Yes
I think you can put the loop between beginTransaction()/commitTransaction() to avoid refresh the UI too many times.
See https://realm.io/docs/java/latest/#adapters, You can just use the adapters from Realm. And in the mEntityListAdapter.setData(realmResults); doesn't seem to be necessary to be called every time if the it is on the same RealmResults.
My Activity is implementing OnInvitationReceivedListener along with all the other game services items. It requires that I have the onInvitationReceived function implemented (i.e. gives an error if I don't) so that's good. The problem is, I can send my account an invite and it will not call the onInvitationReceived function. The other listeners work and I can start a game and whatnot by opening the invitation list and accepting it, but this function simply never gets called. It should also consume the event but I still get an external notification as well.
Am I missing something? Is it not as simple as the below? All the other listeners work...
public class MyActivity extends BaseGameActivity
implements View.OnClickListener, RealTimeMessageReceivedListener,
RoomStatusUpdateListener, RoomUpdateListener, OnInvitationReceivedListener
{
public MyActivity(){...}
...
public void onInvitationReceived(Invitation arg0)
{
Log.v("meh", "Invitation Received");
}
}
Are you registering MyActivity for the callback?
Since you are using BaseGameActivity, try putting this in your post-connection callback:
getGamesClient().registerInvitationListener(this);
I'm somewhat surprised that this isn't done for you, but in looking at the BaseGameActivity class I don't see it.
I was having similar issues. (I still haven't cracked the code on the invite Bundle not being null everytime I start up the service... even when there are invitations waiting...)
From here Previous StackOverflow issue for Invitaion Listener I did get the issue mostly solved. (in that i do get notifications in my app code that a new invite has come in) However, there is nothing to tell you if an invite has been rescinded...
So, I also run a Timer and do this in my code:
#Override
public void loadInvitations(){
mHelper.getGamesClient().loadInvitations(new OnInvitationsLoadedListener() {
#Override
public void onInvitationsLoaded(int statusCode, InvitationBuffer buffer) {
dLog("invitations loaded " + buffer.getCount());
if(mHelper.getGamesClient().STATUS_OK == statusCode && buffer.getCount() > 0){
if(mGHInterface != null){
mGHInterface.haveInvitations(buffer.getCount());
}
} else if (mGHInterface != null){
mGHInterface.haveInvitations(0);
}
}
});
}
Up to you on how often you want to run this, but this way I have found that at least I do know if invitations exist or not, and update my app's actions accordingly.