I have fews questions about RoboSpice (advanced usages).
Note: I use RoboSpice with OrmLite.
My Android application is composed of two main activities: the first one is SplashActivity (start on launch) and the second is MainActivity (lauched 5s after the Splash Screen). I perform 2 requests on splash:
SplashActivity
public class SplashActivity extends BaseActivity {
private fooRequest fooRequest;
private barRequest barRequest;
private exampleRequest exampleRequest;
private NewsRequest newsRequest;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
fooRequest = new fooRequest();
barRequest = new barRequest();
...
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// Launch MainActivity...
}
}, 5000);
}
#Override
protected void onStart() {
super.onStart();
getSpiceManager().execute(fooRequest, new Integer(0), DurationInMillis.ONE_DAY, new fooRequestListener());
getSpiceManager().execute(barRequest, new Integer(1), DurationInMillis.ONE_DAY, new barRequestListener());
}
public final class fooRequestListener implements RequestListener<Foo> {
#Override
public void onRequestFailure(SpiceException spiceException) {
Log.w("MyApp", "RoboSpice - Foo request failure");
}
#Override
public void onRequestSuccess(final Foo result) {
}
}
public final class barRequestListener implements RequestListener<Bar> {
#Override
public void onRequestFailure(SpiceException spiceException) {
Log.w("MyApp", "RoboSpice - Bar request failure");
}
#Override
public void onRequestSuccess(final Bar result) {
}
}
}
The problem
My application logic is not really reliable: we can not be sure that requests are finished when MainAcitivty is launched. On the MainActivity I query my database with OrmLite for fetch some data and display them. So if request started on SplashActivity are not finished, my View display nothing.
Questions
1) I think that I need to add a listener to my pending requests (if such a request exists). On the RoboSpice Wiki, it said to use spiceManager.addListenerToPendingRequest. I have not managed to put it out, despite my tests. Maybe I'm doing something wrong? Can you give me code example? Resolved (see below)
2) Currently, the user is still waiting 5 seconds (timer Splash) before arriving at the home screen. How to check if data are in cache? With spiceManager.getDataFromCache() (it takes into account the expiration duration?).
3) What is the best retry policy to failed requests (a. at the first launched if database is not again created; b. if the database exists but data are expired)?
Edit
Question #1 resolved (I make a mistake in my original code) - #2 and #3 still relevant. Here's what to do (if it can help someone ...):
public class MainActivity extends BaseActivity {
private SpiceManager spiceManager = new SpiceManager(CalendarSpiceService.class);
...
#Override
protected void onStart() {
super.onStart();
spiceManager.start(this);
spiceManager.addListenerIfPending(Foo.class, new Integer(0), new fooRequestListener());
spiceManager.addListenerIfPending(Bar.class, new Integer(2), new newsRequestListener());
spiceManager.getFromCache(Foo.class, new Integer(0), DurationInMillis.ONE_DAY, new fooRequestListener());
spiceManager.getFromCache(Bar.class, new Integer(2), DurationInMillis.ONE_DAY, new newsRequestListener());
}
...
public final class fooRequestListener implements RequestListener<Foo> {
#Override
public void onRequestFailure(SpiceException spiceException) {
Log.w("MyApp", "RoboSpice - Foo request failure");
}
#Override
public void onRequestSuccess(final Foo result) {
String test = result.getResult().iterator().next().getTitle();
Log.w("MyApp", "RoboSpice - Foo request OK ! "+test);
}
}
public final class barRequestListener implements RequestListener<Bar> {
#Override
public void onRequestFailure(SpiceException spiceException) {
Log.w("MyApp", "RoboSpice - Bar request failure");
}
#Override
public void onRequestSuccess(final Bar result) {
Log.w("MyApp", "RoboSpice - Bar request OK ! "+test);
}
}
}
But I don't understand the aim of spiceManager.getFromCache here...
Thanks!
I would make the splash screen last longer. It looks like there is a realy ambiguity on its purpose : displaying a logo or something, or execute requests and display a "please wait message".
In the first case, it would be fine not to execute any request. In the second, it would be fine to wait for them to return.
I really prefer applications that, if they need a splashscreen at all, boot quickly. The splash screen should last less than 1 second. The first screen after it should handle the data querying and please wait message.
But to answer your questions :
2) yes, SpiceManager cache methods use cache expiry in the same way as executeRequest. You can check if there is something in the cache, but you will have to specify what you mean by "valid" data by specifying the expiry limit of the data.
3) I don't see the link with a retry policy and your overall problem. By default, RoboSpice has a retry policy to retry 3 times a failed request. If you can't get the data from your cache then it means there is nothing in it. If your listener's failure hook is invoked, then the network request failed.
Both could be the same listener, and your app should have a general mechanism to 1) display that something is wrong, 2) relaunch request if needed after a while (that could be a refresh button/item menu).
I hope I helped, but not so sure.
Related
I want to develop an Android App with three activities and two services.
The first Service, named WebClientService, calls a REST API every 30 seconds, using an Handler, and has to notify the active Activity with the result.
It also has to notify a second Service, named DatabaseService, in order to update a local DB.
The Database Service will be called just once onCreate of the activity (in case of app crash and restart) and just once at onRestart (in this way we have data to show in case there were connectivity issues). The activities will then keep themselves updated thanks to the WebClientService that notifies the "alive" activity every 30 seconds.
Questions are:
What's the best way to notify for an update both the active activity and the background DatabaseService?
My idea is to use sendBroadcast() within WebClientService and a BroadcastReceiver in every activity and within the DatabaseService, is it the right approach?
Should I use the same approach for the communication between AllMeetingRoomActivity and DatabaseService or should I use a Bound Service?
Thanks
UPDATE:
DatabaseService won't be a background service anymore but just a shared instance of the db layer between WebClientService and the activities.
So question now is: is it a good approach to just write my 30 seconds updates to the local db and allow the activities to update themselves every few seconds simply reading from the local db?
Would that affect the performance too much?
Context:
Follows what I've implemented so far but using SettableFutures and thus needs to be re-implemented using Services and Broadcasts once I've clear how to make them communicate effectively:
public class MainActivity extends AppCompatActivity {
private TextView meetingsTextView;
private EditText mEdit, editSubject;
private final ConnectorInitializer clientInitializer = new ConnectorInitializer();
private AppConnector genericClient; // can use OutlookClient or a test client to talk with a mock server
#Override
protected void onCreate(Bundle savedInstanceState) {
// initializes client based on the settings in "config.json"
genericClient = clientInitializer.create(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
meetingsTextView = (TextView) findViewById(R.id.NowMeeting);
mEdit = (EditText)findViewById(R.id.editText);
editSubject = (EditText)findViewById(R.id.editSubject);
Futures.addCallback(genericClient.logon(this, scopes), new FutureCallback<Boolean>() {
#Override
public void onSuccess(Boolean result) {
Log.d("APP", "-- Logged in. --");
databaseConnector.synchronouslyGetBackupFromLocalDatabase() // FUTURE
// callback here
// onSuccess, onFailure
}
#Override
public void onFailure(#NonNull Throwable t) {
Log.e("\n ~~~~>> logon \n", t.getMessage());
meetingsTextView.setText(R.string.Login_Failed);
}
});
}
/** At the moment the UI is not updated automatically every 30 seconds
* but manually using a refresh button
*/
public void getBookings(#SuppressWarnings("UnusedParameters") View view){
Log.d("APP", "Retrieve button clicked: "+(DateTime.now())+". Calling async getCalendar.");
meetingsTextView.setText(R.string.retrieving_events);
try{
Futures.addCallback( genericClient.getCalendarEvents(), new FutureCallback<String>(){
#Override
public void onSuccess(final String resultCalendars) {
Log.d("APP", "Success. Result: "+resultCalendars);
runOnUiThread(new Runnable() {
#Override
public void run() {
Log.d("APP", "Calendars SUCCESSFULLY retrieved.");
String meetingsRetrieved = getString(R.string.calendar)+resultCalendars;
meetingsTextView.setText(meetingsRetrieved);
Toast.makeText(getApplicationContext(), "Success!", Toast.LENGTH_LONG).show();
}
});
databaseConnector.asyncUpdateLocalDbWithResults(); // FUTURE
// callback here
// onSuccess, onFailure
}
#Override
public void onFailure(#NonNull Throwable t) {
Log.e( "APP", "Calendar error. Cause: "+t.getLocalizedMessage() );
String retrieveError = "Retrieve error. \n\n\n"+t.getLocalizedMessage();
meetingsTextView.setText(retrieveError);
Toast.makeText(getApplicationContext(), "Fail!", Toast.LENGTH_LONG).show();
}
});
}catch(Exception ex){
Log.e("APP","Something went wrong in your code. Cause:"+ex);
}
}
Best option ever:
Use LocalBroadcastManager. More reference here.
MyService.java:
private LocalBroadcastManager localBroadcastManager;
private final String SERVICE_RESULT = "com.service.result";
private final String SERVICE_MESSAGE = "com.service.message";
#Override
public void onCreate() {
super.onCreate();
// Other stuff
localBroadcastManager = LocalBroadcastManager.getInstance(this);
}
Add below method in service, whenever you want to update data from service to Activity, call method by passing Arguments.
private void sendResult(String message) {
Intent intent = new Intent(SERVICE_RESULT);
if(message != null)
intent.putExtra(SERVICE_MESSAGE, message);
localBroadcastManager.sendBroadcast(intent);
}
HomeActivity.java:
private BroadcastReceiver broadcastReceiver;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_home);
broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String s = intent.getStringExtra(MyService.SERVICE_MESSAGE);
// do something here.
}
};
}
#Override
protected void onStart() {
super.onStart();
LocalBroadcastManager.getInstance(this).registerReceiver((broadcastReceiver),
new IntentFilter(MyService.SERVICE_RESULT));
}
#Override
protected void onStop() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
super.onStop();
}
Hope this will help you.
I think your approach is ok with BroadCastReceiver. However, BroadCastReceiver should be used for a global purpose (like communicating between 2 applications). If you intend to use BroadCastReceiver for your app only, I prefer using LocalBroadcastManager instead. Using LocalBroadcastManager is faster and more security when it can be caught only by your app.
There's another way to communicate between your activitys and your services is using EventBus. It will be much easier than using BroadCastReceiver (especially in passing data between them).
Update: About your update question:
is it a good approach to just write my 30 seconds updates to the local db and allow the activities to update themselves every few seconds simply reading from the local db? --> Of course NO. You should let your activities update themselves when they need. When you update your local db, you should know that is there any changes or not. If there is any change, use LocalBroadcastmanager to notify your activity to update.
Would that affect the performance too much? --> Yes, that do. The db connection will take time to execute and it will block your UI in some cases. in that case, you should use a thread with ExecutorService for each execute (insert, update...). One more thing to consider is updating that frequently will drain your phone battery very, very fast.
You can bind the services to the activities and update your UI.
Or you can use libraries like Otto or EventBus to create a publisher/subscriber dependency and notify your activities everytime your services publish an update of information.
Use event bus for this communication. EventBus allows publish-subscribe-style communication between components without requiring the components to explicitly register with one another (and thus be aware of each other). It is designed exclusively to replace traditional Java in-process event distribution using explicit registration.
There are a lot of them:
http://square.github.io/otto/
https://github.com/greenrobot/EventBus
This is an example of Otto usage:
Bus bus = new Bus();
bus.post(new AnswerAvailableEvent(42));
#Subscribe public void answerAvailable(AnswerAvailableEvent event) {
// TODO: React to the event somehow!
}
bus.register(this); // In order to receive events, a class instance needs to register with the bus.
To post from any thread (main or background), in you case a Service and receive events on the main thread:
public class MainThreadBus extends Bus {
private final Handler mHandler = new Handler(Looper.getMainLooper());
#Override
public void post(final Object event) {
if (Looper.myLooper() == Looper.getMainLooper()) {
super.post(event);
} else {
mHandler.post(new Runnable() {
#Override
public void run() {
MainThreadBus.super.post(event);
}
});
}
}
I set my ProfileActivity to extend to AppCompatActivity implementing View.OnClickListener. When the assigned button is clicked,it sends a StringRequest to a url using Volley getting a JSON format and parse it then store it in another class. The data stored is needed on the next activity that is started onClick which is myTabActivity. I need to make the ProfileActivity wait until the request, parsing, and storing is done in the ParseWorkOrderJSON.java before starting myTabActivity. Any thoughts?
I am fairly new to Android too.
Here are snippets from my code:
public class ProfileActivity extends AppCompatActivity implements View.OnClickListener {
public static final String JSON_URL = "/*I ommitted my URL on purpose here*/";
private TextView username_txt;
private Button fetch_data_btn;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_profile);
username_txt = (TextView)findViewById(R.id.txt_username);
fetch_data_btn = (Button)findViewById(R.id.btn_fetch_data);
SharedPreferences sharedPreferences = getSharedPreferences(Config.SHARED_PREF_NAME, Context.MODE_PRIVATE);
String username = sharedPreferences.getString(Config.USERNAME_SHARED_PREF,"Not Available");
username_txt.setText(username);
sendRequest();
fetch_data_btn.setOnClickListener(this);
}
private void sendRequest(){
final ProgressDialog loading = ProgressDialog.show(this,"Fetching data.","Please wait...", false,false);
StringRequest stringRequest = new StringRequest(Request.Method.GET,JSON_URL,
new Response.Listener<String>() {
#Override
public void onResponse(String response) {
System.out.println(response.substring(0));
showJSON(response);
}
},
new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
System.out.println("Something went wrong!");
Toast.makeText(ProfileActivity.this, "Something went wrong!", Toast.LENGTH_LONG).show();
}
});
RequestQueue requestQueue = Volley.newRequestQueue(this);
requestQueue.add(stringRequest);
}
private void showJSON(String json){
ParseWorkOrderJSON pwoj = new ParseWorkOrderJSON();
pwoj.parseWorkOrderJSON(json);
}
#Override
public void onClick(View v){
sendRequest();
Intent intent = new Intent(ProfileActivity.this, MytabActivity.class);
startActivity(intent);
}
}
I am trying to use ProgressDialog while the request, parsing, and storing is happening then dismiss it when all is done. Only then the next activity is loaded which is myTabActivity.
If you would like a snippet of ParseWorkOrderJSON please say so in the comments.
You don't need to slow down your app. You just need a better understanding of how asynchronous operation actually function.
Some operations (like HTTP request) may take a lot of time. If you do them on the main application thread (that is the thread that is in charge of updating and drawing the UI) your users may experience a frozen UI, irresponsive behaviour and all other bar stuff. In fact, Android has a build - in mechanism to prevent against this and, if an app takes too long to process a user input, it will try and force close it (the dreaded ANR dialog).
The way to deal with this is to offload those heavy operations aways from the main thread. Then you have another problem - you need to know when the operations finishes and react accordingly. This is called an async operation - it starts and returns a result in some moment in the future.
You are using Volley - a library, that does all this work for you - it creates a worker thread and uses it to execute HTTP requests, providing you with a callback to get notified when the request is complete.
The problem is that you are starting your activity when you are starting the Volley request. At this point in time there is still no data loaded, and nothing will get displayed. You should wait for the completion of the request, before continuing. How to know when the request is finished - it's when the onResponse() method is called. To have everything working, you should move your code to start your second activity there. That way you will be sure that the data has been loaded and you can proceed to show it to the user.
There are three possible states of request when we enter an activity.
Not yet started
In process, listener is detached
Request complete
From the documentation, I understand if we use execute() in onStart(), it takes care of cases 1,3 but not case 2(when request is in process). We need to use addListenerIfpending() for that
Workaround I used is use addListenerIfPending() in onStart(), and use execute in onRequestNotFound()
protected void onStart(){
super.onStart();
getSpiceManager().addListenerIfPending(my.class,"mykey",new myRequestListener());
}
public final class myRequestListener implements PendingRequestListener<result> {
#Override
public void onRequestFailure(SpiceException spiceException){
}
#Override
public void onRequestSuccess(final RoundInfo roundInfo) {
}
#Override
public void onRequestNotFound(){
getSpiceManager().execute(request,"mykey", DurationInMillis.ONE_DAY,new myRequestListener());
}
}
I want to know if this is the correct way, the way it's meant to be done.
Also, please comment if there will be any performance issues
No, your first statement is wrong.
execute() will take in charge all 3 cases.
addListenerIfPending will not trigger any request by itself, it only allows to plug a listener to an already pending request if such a request exists. So case 2.
It's my first question on SO, I hope this question won't be bad.
I have a service, it starts working when user launchs an app and works until user will kill it via task killer or turn off his device.
This service has a background thread which does some work with data. I need to bind activities (from activities, not by service) and sometimes (1-2 times per 30 seconds) send data to binded activities.
Structure of my service:
public class myserv extends Service {
public static boolean started=false;
public class workwithdata extends Thread {
#Override
public synchronized void start() {
super.start();
//.. Not important.
}
#Override
public void run() {
if (running) return;
while (true) {
if(condition) mythread.sleep(30000);
else {
Object data = recieveMyData();
if (!data.isEmpty()) {
//.. Some work with recieved data, not important.
sendDataToBindedActivities(data); //This is what I need.
}
mythread.sleep(10000);
}
}
}
}
#Override
public void onCreate() {
super.onCreate();
this.started=true;
mythread = new workwithdata();
mythread.start();
}
}
Well, I found one question but my problem has a little differences: I don't need to send any data to the service, I need just send some data to all binded activities (which service doesn't know at all).
Structure for which I'm looking for:
public class myact extends Activity {
#Override
public void onCreate(Bundle bun) {
super.onCreate(bun);
if(!myserv.started) {
Intent service = new Intent(getApplicationContext(), myserv.class);
getApplicationContext().startService(service);
}
bindToService(this);
}
#Override
public void onRecievedData(Object data) {
//work with recieved data from service "myserv".
}
}
I also tried to find some solutions in android documentation but I didn't find what I need.
So, main question is: is it possible to work with communications from service to activities?. If no: What should I use for this purpose? If yes, just, sorry, can I ask for some code or class names, because I tried to find and didn't...
Thank you.
You need to use a RemoteCallbackList
When your clients bind to the service, you will need to register them using RemoteCallbackList.register().
When you want to send data to the bound clients, you do something like this:
int count = callbackList.beginBroadcast();
for (int i = 0; i < count; i++) {
try {
IMyServiceCallback client = callbackList.getBroadcastItem(i);
client.onRecievedData(theData); // Here you callback the bound client's method
// onRecievedData() and pass "theData" back
} catch (RemoteException e) {
// We can safely ignore this exception. The RemoteCallbackList will take care
// of removing the dead object for us.
} catch (Exception e) {
// Not much we can do here except log it
Log.e("while calling back remote client", e);
}
}
callbackList.finishBroadcast();
An example can be found here It is kinda complicated, but maybe you don't need everything this offers. In any case, have a look.
im trying to start the calendar sync programatically using this code
Bundle bundle = new Bundle();
bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
bundle.putBoolean(ContentResolver.SYNC_EXTRAS_FORCE, true);
bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
bundle.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
ContentResolver.requestSync(accounts[0], "com.android.calendar", bundle);
i want a way so i can know when sync complete so i can read data from the calendar
i tried doing this
while (ContentResolver.isSyncActive(accounts[0], "com.android.calendar")) {
System.out.println("looping: " + i);
}
readLocalCalendar();
readLocalEvents();
but the system exit the loop before the sync ends and i can still see the sync sign at the status bar, so any help so i can read calendar events after sync completle done ??
thanks
Another option would be to register a broadcast receiver to tell you when the sync is finished like this:
public class UpdateableActivity extends Activity {
public static final String ACTION_FINISHED_SYNC = "your.package.ACTION_FINISHED_SYNC";
private static IntentFilter syncIntentFilter = new IntentFilter(ACTION_FINISHED_SYNC);
private BroadcastReceiver syncBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// update your views
}
};
#Override
protected void onResume() {
super.onResume();
// register for sync
registerReceiver(syncBroadcastReceiver, syncIntentFilter);
// do your resuming magic
}
#Override
protected void onPause() {
unregisterReceiver(syncBroadcastReceiver);
super.onPause();
}
}
Inside your SyncAdapter use this when done:
getContext().sendBroadcast(new Intent(UpdateableActivity.ACTION_FINISHED_SYNC));
You can also use this for when starting or updating the status of the sync ;)
Update: Updated the code to avoid leaks and making sure the activity is still active (onResume/onPause)
using the addStatusChangeListener actually worked for me .
here's a reference .
don't forget to add the needed permissions .
Use ContentResolver.addStatusChangeListener (int mask, SyncStatusObserver callback) to get notified of changes in sync status. docs
Please do not loop forever, its really bad design. Using the above method everything is asynchronous so you don't waste any cpu cycles.
You could also use ContentResolver.registerContentObserver (Uri uri, boolean notifyForDescendents, ContentObserver observer) docs to get notified in changes on a specific URI (like the calendar's URI)
try an AsyncTask :
private class CustomTask extends AsyncTask<Void, Void, Void>{
#Override
protected Void doInBackground(Void... params) {
// TODO sync your calendar
}
protected void onProgressUpdate(Void... progress) {
//TODO display a spinner or something else to show progress
}
protected void onPostExecute(Void t){
//TODO what you want when doInBackground has finished
}
}
Good luck !