RxJava as an Eventbus between service and activity - android

I have gone through several tutorials such as this and this on the usage of Rxjava as an EventBus. However, they do not solve my problem.
I am making a chat app using my own custom nodejs server and socket.io. Authorization is done via retrofit (with gson). The user should receive messages even if the app is switched off, hence the connection to socket.io is maintained through a perennial service, which will relay information to the activity IF it is displayed to the user, else it simply shows a notification and writes to SQL db (TODO). My Service receives a call from socket.io and parses the payload. When that happens, it needs to send a message to the activity along with the payload received.
MyEventBus.class
public class MyRxBus {
private static MyRxBus instance;
private PublishSubject<Object> subject = PublishSubject.create();
public static MyRxBus instanceOf() {
if (instance == null) {
instance = new MyRxBus();
}
return instance;
}
/**
* Pass any event down to event listeners.
*/
public void setString(Object object) {
subject.onNext(object);
}
/**
* Subscribe to this Observable. On event, do something
* e.g. replace a fragment
*/
public Observable<Object> getEvents() {
return subject;
}
}
SocketService Class
public class SocketService extends Service {
private Socket mSocket;
public SocketService() {
}
#Override
public void onCreate() {
super.onCreate();
Log.d("SERVICE ","SOCKET SERVICE WAS CALLED");
mSocket=SocketSingleton.getSocket("8080");
mSocket.on(Socket.EVENT_CONNECT,onConnect);
mSocket.on("new_message",onNewMessage);
mSocket.connect();
}
private Emitter.Listener onNewMessage = new Emitter.Listener() {
#Override
public void call(Object... args) {
Log.d("Received from socket ",args[0].toString());
try {
final JSONObject object = new JSONObject((String) args[0]);
//Send this object to all subscribers
//TODO: add rxjava eventbus to 1)add info to db, 2)update activity IF it is displayed
MyRxBus.instanceOf()
.setEvent(object);
} catch (JSONException e) {
e.printStackTrace();
}
}
};
//some other functions (not related at all)
}
MainActivity.class
public class MainActivity extends VesicaActivity {
private Socket mSocketOne;
private Gson gson = new Gson();
private List<ChatMessage> mMessages = new ArrayList<ChatMessage>();
private List<String> mListOfUsers = new ArrayList<String>();
Boolean isNodeOne;
String username;
private JSONArray arrayOfUsers=null;
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
private Boolean _isConnected=false;
private String socketIdtoSend;
#BindView(R.id.et_text)EditText etText;
#BindView(R.id.tv_user_list)TextView tvListUsers;
#BindView(R.id.tv_status)TextView tvStatus;
#BindView(R.id.toolbar)Toolbar toolbar;
#BindView(R.id.et_self_destruct_time)EditText etSelfDestructTimer;
#BindView(R.id.cb_self_destruct)CheckBox cbSelfDestruct;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
setSupportActionBar(toolbar);
if (getSupportActionBar()!=null){
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
tvStatus.setText("checking connection...");
username = Constants.getUsername();
Integer portNumber = Constants.getPortNumber();
socketIdtoSend = getIntent().getStringExtra("socketId");
mMessages.add(createMessage("Sample Name","Sample Text",0));
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
mAdapter = new ChatAdapter(mMessages, username);
mRecyclerView.setAdapter(mAdapter);
//-----------------------------------------------------------------
MyRxBus.instanceOf()
.getEvents()
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {
final JSONObject object = new JSONObject((String) o)
ChatMessage message = createMessage(object.getString("user")
,Encryption.Decrypt(object.getString("message"))
,object.getInt("selfDestructTime"));
mMessages.add(message);
mAdapter.notifyDataSetChanged();
Toast.makeText(MainActivity.this," new message received ", Toast.LENGTH_SHORT).show();
}
});
}
public ChatMessage createMessage(String user, String message, Integer time){
return new ChatMessage.ChatMessageBuilder()
.message(message)
.user(user)
.type("newMessage")
.selfDestructTime(time)
.sendToSocketId(socketIdtoSend)
.build();
}
public void showDialogBoxToClearHistory(){
new AlertDialog.Builder(MainActivity.this)
.setTitle("Wipe Chat History")
.setMessage("This will delete the entire chat history of this thread. It is irreversible. Do you want to continue?")
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// continue with delete
mMessages.clear();
mAdapter.notifyDataSetChanged();
Toast.makeText(MainActivity.this, "Cleared entire history", Toast.LENGTH_LONG).show();
}
})
.setNegativeButton("Skip", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
try{
dialog.dismiss();
}catch (Exception e){e.printStackTrace();}
}
})
.setCancelable(false)
.show();
}
private void ScrollToBottom(){
mRecyclerView.smoothScrollToPosition(mAdapter.getItemCount());
}
}
When I run this code, I get the following error
rx.exceptions.OnErrorNotImplementedException: Can't create handler inside thread that has not called Looper.prepare()
that points to
at com.example.varun.vesica.eventbus.MyRxBus.setEvent(MyRxBus.java:26)
I am aware that observables and observers can be set to emit/ observe of different threads, however after lot of trial and error, I have given up and need some help.
Any help/ links will be appreciated. Thanks!

Maintainin connection all time will drain user's battery. You should consider using push notification when device is blocked or app is minimized.
When you subscribe to Observable in Activity, save Subscription and close it in onPause, otherwise there will be lingering connections caused by device rotation.
Consider using GreenRobot event bus instead of reinventing the wheel, it is very convenient library for connecting Service and Activity: http://www.andreas-schrade.de/2015/11/28/android-how-to-use-the-greenrobot-eventbus/

You are attempting to call adapter.notifyDataSetChanged() and Toast.makeText() on whatever arbitrary thread invoked setEvent()/setString().
When you subscribe to your getEvents() observable with the goal of updating Android UI, you need to observe on the main thread scheduler:
MyRxBus.instanceOf()
.getEvents()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<Object>() {
#Override
public void call(Object o) {
final JSONObject object = new JSONObject((String) o)
ChatMessage message = createMessage(object.getString("user")
,Encryption.Decrypt(object.getString("message"))
,object.getInt("selfDestructTime"));
mMessages.add(message);
mAdapter.notifyDataSetChanged();
Toast.makeText(MainActivity.this," new message received ", Toast.LENGTH_SHORT).show();
}
});

Related

Android Pusher Singleton channel subscription

Hello I have implemented pusher for realtime chat and subscribing to pusher channel , but I have many activities and fragments where i want to listen to pushr events . I have added this code in every activity/fragment but the problem is that it creates multiple subscriptions for every id . I know that i have to use Singleton for this can anyone point me in the right direction to achieve this ?
Here is the code i am writing in every activity/fragment
private PusherOptions options;
private Channel channel;
private Pusher pusher;
options = new PusherOptions();
options.setCluster("ap2");
pusher = new Pusher("afbfc1f591fd7b70190f", options);
pusher.connect();
profile_id = Global.shared().preferences.getString("PROFILE_ID", " ");
channel = pusher.subscribe(profile_id);
channel.bind("message",
new SubscriptionEventListener() {
#Override
public void onEvent(String s, String s1, final String data) {
runOnUiThread(new Runnable() {
#Override
public void run() {
try {
JSONObject result = new JSONObject(data);
String message = result.getString("message");
String time = result.getString("time");
String reId = result.getString("recieverId");
new_message = message;
getConvoData(k, message);
} catch (JSONException e) {
e.printStackTrace();
}
System.out.println("DATA ====>>" + data);
}
});
}
});
okay so after trying for a while i figured it out my self i created a global class and just added pusher code to it so that it maintains just one connection for the entire lifecycle of the app
public class Global extends MultiDexApplication {
#Override
public void onCreate() {
super.onCreate();
SharedPreferences preferences = sharedInstance.getSharedPreferences(sharedInstance.getString(R.string.shared_preferences), Context.MODE_PRIVATE);
sharedInstance.preferences = preferences;
connectTopusher();
}
public void connectTopusher() {
PusherOptions options;
Channel channel;
Pusher pusher;
options = new PusherOptions();
options.setCluster("ap2");
pusher = new Pusher("afbfc1f591fd7b70190f", options);
pusher.connect();
String profile = Global.shared().preferences.getString("PROFILE_ID", "");
channel = pusher.subscribe(profile);
channel.bind("message",
new SubscriptionEventListener() {
#Override
public void onEvent(String s, String s1, final String data) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
try {
JSONObject result = new JSONObject(data);
String message = result.getString("message");
String time = result.getString("time");
String reId = result.getString("recieverId");
} catch (JSONException e) {
e.printStackTrace();
}
System.out.println("DATA ====>>" + data);
}
});
}
});
channel.bind("status_change", new SubscriptionEventListener() {
#Override
public void onEvent(String s, String s1, final String data) {
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
try {
JSONObject result = new JSONObject(data);
} catch (JSONException e) {
e.printStackTrace();
}
System.out.println("DATA ====>>" + data);
}
});
}
});
}
You can expose channel in your Global class. That will allow you to call bind and unbind in your fragments, when they are in the foreground.
connectToPusher should just create a channel and subscribe to it.
In Global.java:
private Channel channel;
public void connectTopusher() {
PusherOptions options;
Pusher pusher;
options = new PusherOptions();
options.setCluster("ap2");
pusher = new Pusher("afbfc1f591fd7b70190f", options);
pusher.connect();
String profile = Global.shared().preferences.getString("PROFILE_ID", "");
this.channel = pusher.subscribe(profile);
}
public Channel getChannel(){
return this.channel;
}
And then in your activity/fragment you can bind/unbind your listeners to when they are resumed/paused - just keep a reference to it like this:
YourActivity.java (could also be your Fragment)
private SubscriptionEventListener messageListener = new SubscriptionEventListener(){
#Override
public void onEvent(String channel, String event, String data) {
//TODO: do something with events
}
}
//Bind when the listener comes into the foreground:
#Override
protected void onResume() {
super.onResume();
((Global) getActivity().getApplication()).getChannel().bind("message", messageListener);
}
//Make sure to unbind the event listener!
#Override
protected void onPause() {
super.onPause();
((Global) getActivity().getApplication()).getChannel().unbind("message", messageListener);
}
I hope this helps :)

How to (RxJava) properly set conditions on which handler will process data, emitted from Observable?

So, my task is to push my device's Location info, when it changes, to the remote server Json API service. If remote server is unavailable, my DatabaseManager must save them to a local database.
Here is my Retrofit API:
public interface GpsService {
#POST("/v1/savelocationbatch")
SaveResponse saveLocationBatch(#Body LocationBatch locationBatch);
}
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(myBaseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
GpsService service = retrofit.create(GpsService.class);
And a POJO class:
public class LocationBatch{
#SerializedName("LocationPointList")
ArrayList<LocationPoint> locationPointList;
#SerializedName("ClientId")
String clientId;
#SerializedName("ClientSecret")
String clientSecret;
//setter & getter
}
My LocationPoint model:
#Table(name="LocationPoints", id = "_id")
public class LocationPoint extends Model {
#SerializedName("Latitude")
#Column(name="latitude")
public Double latitude;
#SerializedName("Longitude")
#Column(name="longitude")
public Double longitude;
#SerializedName("Altitude")
#Column(name="altitude")
public Double altitude;
//... setters, getters etc
}
All of my last locations are stored in a CurrentLocationHolder singleton (for batch sending/saving to DB/emitting from Observable). It's setLocation() method updates currentLocation variable, then puts it to the locationBuffer, than checks the buffer's size, than if buffer's size exceeds my MAX_BUFFER_SIZE variable, it fires locationBufferChanged.onNext(with a copy of a locationBuffer as argument), then it clears locationBuffer...
public class CurrentLocationHolder {
private List<LocationPoint> locationBuffer =
Collections.synchronizedList(new ArrayList<>());
private LocationPoint currentLocation;
private final PublishSubject<List<LocationPoint>> locationBufferFull =
PublishSubject.create();
public Observable<List<LocationPoint>>
observeLocationBufferFull(boolean emitCurrentValue) {
return emitCurrentValue ?
locationBufferFull.startWith(locationBuffer) :
locationBufferFull;
}
public void setLocation(LocationPoint point) {
this.currentLocation = point;
locationBuffer.add(point);
if (locationBuffer.size() >= MAX_BUFFER_SIZE) {
locationBufferChanged.onNext(new ArrayList<>(this.locationBuffer));
}
locationBuffer.clear();
}
}
And here is my DatabaseManager:
public class DatabaseManager {
private Subscription locationBufferSubscription;
private static DatabaseManager instance;
public static void InitInstance() {
if (instance == null)
instance = new DatabaseManager();
}
}
public void saveToDb(ArrayList<LocationPoint> locArray){
ActiveAndroid.beginTransaction();
try {
for (int i = 0; i < locArray.size(); i++) {
locArray.get(i).save();
}
ActiveAndroid.setTransactionSuccessful();
}
finally {
ActiveAndroid.endTransaction();
}
}
}
My application's main goal:
To write all of the listened LocationPoints to the HTTP server through Retrofit. If a remote server will be suddenly down for some reason (or internet connection would lost), my app should seamlessly write new locationPoints to a local database. When the server (or internet) will be up, some mechanism should provide saved local data to Retrofit's call.
So, my questions are:
How to create an Rx-Observable object, which will emit List normally to a Retrofit service, but when server (or internet) goes down, it should provide unsaved LocationPoints to DatabaseManager.saveToDb() method?
How to catch internet connection or server "up" state? Is it a good idea to create some Observable, which will ping my remote server, and as result should emit some boolean value to it's subscribers? What is the best way to implement this behavior?
Is there a simple way to enqueue Retrofit calls with a locally saved data (from local DB), when internet connection (server) will become "up"?
How not to loose any of my LocationPoints on the server-side? (finally my client app must send all of them!
Am I doing something wrong? I am a newbie to Android, Java and
particularly to RxJava...
Interesting task! First of all: you don't need to create DB for storing such tiny info. Android has good place for storing any Serializable data.
So for saving locally data crate Model like:
public class MyLocation implements Serializable {
#Nonnull
private final String id;
private final Location location;
private final boolean isSynced;
// constructor...
// getters...
}
Singleton class:
public class UserPreferences {
private static final String LOCATIONS = "locations";
#Nonnull
private final SharedPreferences preferences;
#Nonnull
private final Gson gson;
private final PublishSubject<Object> locationRefresh = PublishSubject.create();
public void addLocation(MyLocation location) {
final String json = preferences.getString(LOCATIONS, null);
final Type type = new TypeToken<List<MyLocation>>() {
}.getType();
final List<MyLocation> list;
if (!Strings.isNullOrEmpty(json)) {
list = gson.fromJson(json, type);
} else {
list = new ArrayList<MyLocation>();
}
list.add(lication);
final String newJson = gson.toJson(set);
preferences.edit().putString(LOCATIONS, newJson).commit();
locationRefresh.onNext(null);
}
private List<String> getLocations() {
final String json = preferences.getString(LOCATIONS, null);
final Type type = new TypeToken<List<MyLocation>>() {
}.getType();
final List<MyLocation> list = new ArrayList<MyLocation>();
if (!Strings.isNullOrEmpty(json)) {
list.addAll(gson.<List<MyLocation>>fromJson(json, type));
}
return list;
}
#Nonnull
public Observable<List<MyLocation>> getLocationsObservable() {
return Observable
.defer(new Func0<Observable<List<MyLocation>>>() {
#Override
public Observable<List<MyLocation>> call() {
return Observable.just(getLocations())
.filter(Functions1.isNotNull());
}
})
.compose(MoreOperators.<List<MyLocation>>refresh(locationRefresh));
}
// also You need to create getLocationsObservable() and getLocations() methods but only for not synced Locations.
}
Change:
public interface GpsService {
#POST("/v1/savelocationbatch")
Observable<SaveResponse> saveLocationBatch(#Body LocationBatch locationBatch);
}
Now the most interesting...make it all works.
There is extention for RxJava. It has a lot of "cool tools" (btw, MoreOperators in UserPref class from there), also it has something for handling retrofit errors.
So let assume that location saving suppose to happens, when Observable saveLocationObservable emit something. In that case your code looks like:
final Observable<ResponseOrError<SaveResponse>> responseOrErrorObservable = saveLocationObservable
.flatMap(new Func1<MyLocation, Observable<ResponseOrError<SaveResponse>>>() {
#Override
public Observable<ResponseOrError<SaveResponse>> call(MyLocation myLocation) {
final LocationBatch locationBatch = LocationBatch.fromMyLocation(myLocation); // some method to convert local location to requesr one
return saveLocationBatch(locationBatch)
.observeOn(uiScheduler)
.subscribeOn(networkScheduler)
.compose(ResponseOrError.<SaveResponse>toResponseOrErrorObservable());
}
})
.replay(1)
.refCount();
final Observable<Throwable> error = responseOrErrorObservable
.compose(ResponseOrError.<SaveResponse>onlyError())
.withLatestFrom(saveLocationObservable, Functions2.<MyLocation>secondParam())
.subscribe(new Action1<MyLocation>() {
#Override
public void call(MyLocation myLocation) {
// save location to UserPref with flag isSynced=flase
}
});
final Observable<UserInfoResponse> success = responseOrErrorObservable
.compose(ResponseOrError.<SaveResponse>onlySuccess())
.subscribe(new Action1<SaveResponse>() {
#Override
public void call(SaveResponse response) {
// save location to UserPref with flag isSynced=true
}
});

What's a good way to asynchronously update the progress of ProgressDialog from background thread?

I want to have a Splash screen that has an inderteminate ProgressDialog and its progress gets updated by async calls from within a Presenter class (from MVP architecture).
I have a number of API calls to make to my BaaS server and for every successfull call, I would like to update the progress bar.
What's the best way to accomplish this?
I have been trying using EventBus to send notifications to my SplashActivity but it seems that all the API calls are first completed and only then the bus notifications are getting consumed and updating the UI.
What I have done so far is:
SplashActivity:
#Subscribe(threadMode = ThreadMode.MAIN)
public void onProgressBar(String event) {
Timber.d("onProgressBar");
if(event.contains("Done")) {
roundCornerProgressBar.setProgress(100);
} else {
roundCornerProgressBar.setProgress(roundCornerProgressBar.getProgress() + 10);
}
textViewTips.setText(event);
}
Presenter:
InstanceID iid = InstanceID.getInstance(ctx);
String id = iid.getId();
mDataManager.getPreferencesHelper().putInstanceId(id);
GSUtil.instance().deviceAuthentication(id, "android", mDataManager);
GSUtil.instance().getPropertySetRequest("PRTSET", mDataManager);
GSUtil:
public void deviceAuthentication(String deviceId, String deviceOS, final DataManager mDataManager) {
gs.getRequestBuilder().createDeviceAuthenticationRequest()
.setDeviceId(deviceId)
.setDeviceOS(deviceOS)
.send(new GSEventConsumer<GSResponseBuilder.AuthenticationResponse>() {
#Override
public void onEvent(GSResponseBuilder.AuthenticationResponse authenticationResponse) {
if(mDataManager != null) {
mDataManager.getPreferencesHelper().putGameSparksUserId(authenticationResponse.getUserId());
}
EventBus.getDefault().post("Reading player data");
}
});
}
public void getPropertySetRequest(String propertySetShortCode, final DataManager mDataManager) {
gs.getRequestBuilder().createGetPropertySetRequest()
.setPropertySetShortCode(propertySetShortCode)
.send(new GSEventConsumer<GSResponseBuilder.GetPropertySetResponse>() {
#Override
public void onEvent(GSResponseBuilder.GetPropertySetResponse getPropertySetResponse) {
GSData propertySet = getPropertySetResponse.getPropertySet();
GSData scriptData = getPropertySetResponse.getScriptData();
try {
JSONObject jObject = new JSONObject(propertySet.getAttribute("max_tickets").toString());
mDataManager.getPreferencesHelper().putGameDataMaxTickets(jObject.getInt("max_tickets"));
jObject = new JSONObject(propertySet.getAttribute("tickets_refresh_time").toString());
mDataManager.getPreferencesHelper().putGameDataTicketsRefreshTime(jObject.getLong("refresh_time"));
} catch (JSONException e) {
e.printStackTrace();
}
EventBus.getDefault().post("Game data ready");
EventBus.getDefault().post("Done!");
}
});
}
Right now I am just showing you 2 API calls, but I will need another 2.
Thank you
I found the answer! It's easier that I thought, which is unfortunate as I spend about 4 hours on this:
First, I created two new methods on my MVPView interface:
public interface SplashMvpView extends MvpView {
void updateProgressBarWithTips(float prog, String tip);
void gameDataLoaded();
}
Then, in the presenter itself, I call every API call and for every call, I update the View with the updateProgressBarWithTips method and when everything is completed, I finalise it so I can move from Splash screen to Main screen:
private void doGSData(String id) {
getMvpView().updateProgressBarWithTips(10, "Synced player data");
GSAndroidPlatform.gs().getRequestBuilder().createDeviceAuthenticationRequest()
.setDeviceId(id)
.setDeviceOS("android")
.send(new GSEventConsumer<GSResponseBuilder.AuthenticationResponse>() {
#Override
public void onEvent(GSResponseBuilder.AuthenticationResponse authenticationResponse) {
if(mDataManager != null) {
mDataManager.getPreferencesHelper().putGameSparksUserId(authenticationResponse.getUserId());
}
getMvpView().updateProgressBarWithTips(10, "Synced game data");
GSAndroidPlatform.gs().getRequestBuilder().createGetPropertySetRequest()
.setPropertySetShortCode("PRTSET")
.send(new GSEventConsumer<GSResponseBuilder.GetPropertySetResponse>() {
#Override
public void onEvent(GSResponseBuilder.GetPropertySetResponse getPropertySetResponse) {
GSData propertySet = getPropertySetResponse.getPropertySet();
GSData scriptData = getPropertySetResponse.getScriptData();
try {
JSONObject jObject = new JSONObject(propertySet.getAttribute("max_tickets").toString());
mDataManager.getPreferencesHelper().putGameDataMaxTickets(jObject.getInt("max_tickets"));
jObject = new JSONObject(propertySet.getAttribute("tickets_refresh_time").toString());
mDataManager.getPreferencesHelper().putGameDataTicketsRefreshTime(jObject.getLong("refresh_time"));
} catch (JSONException e) {
e.printStackTrace();
}
getMvpView().gameDataLoaded();
}
});
}
});
}
I hope this helps someone, if you're using MVP architecture.
Cheers

Salesforce Rest API with android - NullPointerException # AsyncRequestCallback

I'm trying to get the Salesforce REST API working with Android and new to android programming, followed the sample code to connect with SFDC http://wiki.developerforce.com/page/Getting_Started_with_the_Mobile_SDK_for_Android#Authentication
I'm trying to get a few records from SFDC and display them in the android app, looks like when the Async Call is made at "client.sendAsync(sfRequest, new AsyncRequestCallback()" - NullPointerException is thrown.
I did see a couple of similar issues online, but didn't help me. Hoping if some one would point me in the right direction to troubleshoot this. Thanks much.
public class GetAccountsActivity extends Activity {
private PasscodeManager passcodeManager;
private String soql;
private String apiVersion;
private RestClient client;
private TextView resultText;
private RestRequest sfRequest;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Get Api Version
apiVersion = getString(R.string.api_version);
//Create Query
soql = "select id, name from Account limit 10";
// Setup view
setContentView(R.layout.get_accounts_activity);
((TextView) findViewById(R.id.Acc_Title)).setText(apiVersion);
// Passcode manager
passcodeManager = ForceApp.APP.getPasscodeManager();
}
#Override
public void onResume() {
super.onResume();
//Get SFClient
// Login options
String accountType = ForceApp.APP.getAccountType();
LoginOptions loginOptions = new LoginOptions(
null, // login host is chosen by user through the server picker
ForceApp.APP.getPasscodeHash(),
getString(R.string.oauth_callback_url),
getString(R.string.oauth_client_id),
new String[] {"api"});
new ClientManager(this, accountType, loginOptions).getRestClient(this, new RestClientCallback() {
#Override
public void authenticatedRestClient(RestClient client) {
if (client == null) {
ForceApp.APP.logout(GetAccountsActivity.this);
return;
}
GetAccountsActivity.this.client = client;
}
});
//Get Rest Object to query
try {
sfRequest = RestRequest.getRequestForQuery(apiVersion, soql);
//Use SF Rest Client to send the request
client.sendAsync(sfRequest, new AsyncRequestCallback(){
#Override
public void onSuccess(RestRequest request, RestResponse response){
//Check responses and display results
// EventsObservable.get().notifyEvent(EventType.RenditionComplete);
}//end onSuccess
#Override
public void onError(Exception exception) {
//printException(exception);
EventsObservable.get().notifyEvent(EventType.RenditionComplete);
}//End Exception for Async Method
});
}catch (UnsupportedEncodingException e) {
//printHeader("Could Send Query request");
//printException(e);
return;
}
}
}
enter code here
You are calling client.sendAsync from onResume() but client is not set until the authenticatedRestClient callback is called, you need to move your sendAsync call into the authenticatedRestClient callback.

how to pass data between service and it's application in the right way?

i'm a newbie in android. In my app i create a many-to-many chat, and need to update from server a list of Messages. In order to do so, i created a service that updates every second from the server.
My problem is that i don't know how to pass data back to the application. I know that I should do it using intent and broadcast receiver, but in that I stuck with Bundle object that i have to serialize in order to pass it to the app, and it does not make sense to me, since this operation is not that efficient.
For now i'm using the ref to my application (i think it's not that good but don't know why), and after every update from server in the service i activate the application function, and updates it's fields directly. Moreover i think maybe my code will do some good for beginners as well :)
public class UpdateChatService extends Service {
private static final long DELAY_FOR_CHAT_TASK = 0;
private static final long PERIOD_FOR_CHAT_TASK = 1;
private static final TimeUnit TIME_UNIT_CHAT_TASK = TimeUnit.SECONDS;
//private Task retryTask; TODO: check this out
private ScheduledExecutorService scheduler;
private boolean timerRunning = false;
private long RETRY_TIME = 200000;
private long START_TIME = 5000;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
scheduleChatUpdate();
}
private void scheduleChatUpdate() {
BiggerGameApp app = (BiggerGameApp) getApplication();
this.scheduler = Executors.newScheduledThreadPool(3);
this.scheduler.scheduleAtFixedRate(new UpdateChatTask(app),
DELAY_FOR_CHAT_TASK, PERIOD_FOR_CHAT_TASK,
TIME_UNIT_CHAT_TASK);
timerRunning = true;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!timerRunning) {
scheduleChatUpdate();
}
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onDestroy() {
super.onDestroy();
if (scheduler != null) {
scheduler.shutdown();
}
timerRunning = false;
}
}
Here is the code of the asynchronous task the runs in the service.
Please tell me what i'm doing wrong, and how should pass data from the service to the application.
public void run() {
try {
if (this.app.getLastMsgFromServer() == null) {
this.app.setLastMsgFromServer(new Message(new Player(DEFAULT_EMAIL), "", -1));
this.app.getLastMsgFromServer().setMessageId(-1);
}
Gson gson = new GsonBuilder()
.registerTypeAdapter(DateTime.class, new DateTimeTypeConverter())
.create();
ServerHandler serverHandler = new ServerHandler();
String jsonString = gson.toJson(this.app.getLastMsgFromServer());
// Sending player to servlet in server
String resultString = serverHandler.getResultFromServlet(jsonString, "GetListOfMessages");
if (resultString.contains("Error")) {
return;
}
// Parsing answer
JSONObject json = new JSONObject(resultString);
Status status = null;
String statusString = json.getString("status");
if (statusString == null || statusString.length() == 0)
return;
status = Status.valueOf(statusString);
if (Status.SUCCESS.equals(status)) {
ArrayList<Message> tempChat = null;
JSONArray jsonList = json.getJSONArray("data");
MyJsonParser jsonParser = new MyJsonParser();
tempChat = jsonParser.getListOfMessagesFromJson(jsonList.toString());
if (tempChat != null && tempChat.size() != 0) {
// After getting the chat from the server, it saves the last msg
// For next syncing with the server
this.app.setLastMsgFromServer(tempChat.get(LAST_MSG_INDEX));
tempChat.addAll(this.app.getChat());
if (tempChat.size() > SIZE_OF_USER_CHAT) {
tempChat = (ArrayList<Message>) tempChat.subList(0, SIZE_OF_USER_CHAT - 1);
}
this.app.setChat(tempChat);
this.app.updateViews(null);
}
}
return;
Is the Service local only (I'm going to assume "yes")?
Communication with a local-only service can be done by passing an instance of android.os.Binder back, as shown below:
public class UpdateChatService extends Service {
public static final class UpdateChat extends Binder {
UpdateChatService mInstance;
UpdateChat(UpdateChatService instance) {
mInstance = instance;
}
public static UpdateChat asUpdateChat(IBinder binder) {
if (binder instanceof UpdateChat) {
return (UpdateChat) binder;
}
return null;
}
public String pollMessage() {
// Takes a message from the list or returns null
// if the list is empty.
return mInstance.mMessages.poll();
}
public void registerDataSetObserver(DataSetObserver observer) {
mInstance.mObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mInstance.mObservable.unregisterObserver(observer);
}
}
private ScheduledExecutorService mScheduler;
private LinkedList<String> mMessages;
private DataSetObservable mObservable;
#Override
public IBinder onBind(Intent intent) {
return new UpdateChat(this);
}
#Override
public void onCreate() {
super.onCreate();
mObservable = new DataSetObservable();
mMessages = new LinkedList<String>();
mScheduler = Executors.newScheduledThreadPool(3);
mScheduler.scheduleAtFixedRate(new UpdateChatTask(), 0, 1, TimeUnit.SECONDS);
}
#Override
public void onDestroy() {
super.onDestroy();
mScheduler.shutdownNow();
mObservable.notifyInvalidated();
}
class UpdateChatTask implements Runnable {
int mN = 0;
public void run() {
// This example uses a list to keep all received messages, your requirements may vary.
mMessages.add("Message #" + (++mN));
mObservable.notifyChanged();
}
}
}
This example could be used to feed an Activity (in this case a ListActivity) like this:
public class ChattrActivity extends ListActivity implements ServiceConnection {
LinkedList<String> mMessages;
ArrayAdapter<String> mAdapter;
UpdateChat mUpdateChat;
DataSetObserver mObserver;
Runnable mNotify;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMessages = new LinkedList<String>();
mNotify = new Runnable() {
public void run() {
mAdapter.notifyDataSetChanged();
}
};
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mMessages);
getListView().setAdapter(mAdapter);
// Bind to the Service if you do not need it to persist when this Activity
// dies - otherwise you must call #startService(..) before!
bindService(new Intent(this, UpdateChatService.class), this, BIND_AUTO_CREATE);
}
/**
* #see android.app.ListActivity#onDestroy()
*/
#Override
protected void onDestroy() {
super.onDestroy();
if (mUpdateChat != null) {
mUpdateChat.unregisterDataSetObserver(mObserver);
unbindService(this);
}
}
public void onServiceConnected(ComponentName name, IBinder service) {
mUpdateChat = UpdateChat.asUpdateChat(service);
mObserver = new DataSetObserver() {
#Override
public void onChanged() {
String message;
while ((message = mUpdateChat.pollMessage()) != null) {
mMessages.add(message);
}
runOnUiThread(mNotify);
}
#Override
public void onInvalidated() {
// Service was killed - restart or handle this error somehow.
}
};
// We use a DataSetObserver to notify us when a message has been "received".
mUpdateChat.registerDataSetObserver(mObserver);
}
public void onServiceDisconnected(ComponentName name) {
mUpdateChat = null;
}
}
If you need to communicate across processes you should look into implementing an AIDL interface - but for "local" versions this pattern works just fine & doesn't involve abusing the global Application instance.
You can use a static memory shared between your service and rest of application (activities). If you do not plan to expose this service to external apps, then sharing static memory is better than serializing/deserializing data via bundles.
Bundles based approach is encouraged for components that are to be exposed to outside world. A typical app usually has just the primary activity exposed in app manifest file.
If your don't pulibc your service , the static memory and the callback function can do.
If not , you can send broadcast.

Categories

Resources