Android Activity and Service communication: how to keep the UI up-to-date - android-activity

I have an Activity A (not the main Activity) that launches a Service S that does some stuff in the background and, in the meanwhile, should made some changes to the UI of A.
Let's just say that S count from 0 to 100 and A should display this count in Real-Time. Since the real job of S is quite more complicated and CPU-consuming, I do not want to use AsyncTask for it (indeed "AsyncTasks should ideally be used for short operations (a few seconds at the most.) [...]") but just a regular Service started in a new Thread (an IntentService would be fine as well).
This is my Activity A:
public class A extends Activity {
private static final String TAG = "Activity";
private TextView countTextView; // TextView that shows the number
Button startButton; // Button to start the count
BResultReceiver resultReceiver;
/**
* Receive the result from the Service B.
*/
class BResultReceiver extends ResultReceiver {
public BResultReceiver(Handler handler) {
super(handler);
}
#Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
switch ( resultCode ) {
case B.RESULT_CODE_COUNT:
String curCount = resultData.getString(B.RESULT_KEY_COUNT);
Log.d(TAG, "ResultReceived: " + curCount + "\n");
runOnUiThread( new UpdateUI(curCount) ); // NOT WORKING AFTER onResume()!!!
break;
}
}
}
/**
* Runnable class to update the UI.
*/
class UpdateUI implements Runnable {
String updateString;
public UpdateUI(String updateString) {
this.updateString = updateString;
}
public void run() {
countTextView.setText(updateString);
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.counter);
countTextView = (TextView) findViewById(R.id.countTextView);
startButton = (Button) findViewById(R.id.startButton);
resultReceiver = new BResultReceiver(null);
}
public void startCounting(View view) {
startButton.setEnabled(false);
//Start the B Service:
Intent intent = new Intent(this, B.class);
intent.putExtra("receiver", resultReceiver);
startService(intent);
}
}
And this is my Service B:
public class B extends Service {
private static final String TAG = "Service";
private Looper serviceLooper;
private ServiceHandler serviceHandler;
private ResultReceiver resultReceiver;
private Integer count;
static final int RESULT_CODE_COUNT = 100;
static final String RESULT_KEY_COUNT = "Count";
/**
* Handler that receives messages from the thread.
*/
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
#Override
public void handleMessage(Message msg) {
while ( count < 100 ) {
count++;
//Sleep...
sendMessageToActivity(RESULT_CODE_COUNT, RESULT_KEY_COUNT, count.toString());
}
//Stop the service (using the startId to avoid stopping the service in the middle of handling another job...):
stopSelf(msg.arg1);
}
}
#Override
public void onCreate() {
//Start up the thread running the service:
HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
this.count = 0;
//Get the HandlerThread's Looper and use it for our Handler
serviceLooper = thread.getLooper();
serviceHandler = new ServiceHandler(serviceLooper);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
this.resultReceiver = intent.getParcelableExtra("receiver");
//For each start request, send a message to start a job and deliver the start ID so we know which request we're stopping when we finish the job:
Message msg = serviceHandler.obtainMessage();
msg.arg1 = startId;
serviceHandler.sendMessage(msg);
//If we get killed, after returning from here, restart:
return START_REDELIVER_INTENT;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* Send a message from to the activity.
*/
protected void sendMessageToActivity(Integer code, String name, String text) {
Bundle bundle = new Bundle();
bundle.putString(name, text);
//Send the message:
resultReceiver.send(code, bundle);
}
}
Everything works fine but if I click the back button (or the home button) and then I re-open the Activity A then the UI of A is not updated anymore (it just shows the initial configuration of A - i.e. the "startButton" is still clickable and the count is not showed - it seems runOnUiThread(...) is not working anymore). However, the Service B is still running in the background and the I can see the correct count is passed to the Activity A in the Log.d(...). Finally, if I click on the "startButton" again, the counting does not start from the beginning (0) but from where B has been arrived (I've double checked it by showing it in the notification bar).
How can I fix this behaviour? I would like that, when I re-open the Activity A, it automatically continues to receive and update the data from the Service B. Or, in other words, that the Service keeps the UI of the Activity A up-to-date.
Please give me some hints, links or piece of code. Thanks!

When you click back button your Activity is destroyed. When you start the Activity again you get a new Activity. That also happen when you rotate the device. This is Android lifecycle event
The Activity is not good for heavy Business Logic only to show stuff/control stuf.
What you have to do is create a simple MVC, Model View Controller. The view (Activity) should only be used for showing results and controlling the eventflow.
The Service can hold an Array of the count and when your Activity start it will onBind() your Service that is running (or if not running will start the Service since you bind to it) Let the Activity(View) get the Array of results and show it. This simple setup exclude the (M)Model Business Logic.
Update
Following up a bit read this it's Android official docs and perfect start since it do kind of what you asking. As you see in the example in the onStart() the Activity establish a connection with the service and in the onStop() the connection is removed. There's no point having a connection after on onStop(). Just like you asking for. I would go with this setup and not let the Service continuously sending data because that would drain resources and the Activity is not always listening because it will stop when in the background.
Here's an activity that binds to LocalService and calls getRandomNumber() when a button is clicked:

Related

Why is onChanged() not called when observing LiveData

This question is a follow up to this problem here: How to make retrofit API call using ViewModel and LiveData
The mistakes 1 and 2 highlighted in that post's response have been fixed. For mistake 3, I haven't yet moved the API call to a repository, but I will once the code start working properly.
So I'm trying to make an API call using Retrofit, using MVVM with LiveData and ViewModel. The API call (which currently is in the ViewModel), is working properly, but the changes is not being picked up by the Observer in the Activity.
I've setup my ViewModel observer as follow:
public class PopularGamesActivity extends AppCompatActivity {
private static final String igdbBaseUrl = "https://api-endpoint.igdb.com/";
private static final String FIELDS = "id,name,genres,cover,popularity";
private static final String ORDER = "popularity:desc";
private static final int LIMIT = 30;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_popular_games);
PopularGamesViewModel popViewModel = ViewModelProviders.of(this).get(PopularGamesViewModel.class);
popViewModel.getGameList().observe(this, new Observer<List<Game>>() {
#Override
public void onChanged(#Nullable List<Game> gameList) {
String firstName = gameList.get(0).getName();
Timber.d(firstName);
}
And my ViewModel code is as follow:
public class PopularGamesViewModel extends AndroidViewModel {
private static final String igdbBaseUrl = "https://api-endpoint.igdb.com/";
private static final String FIELDS = "id,name,genres,cover,popularity";
private static final String ORDER = "popularity:desc";
private static final int LIMIT = 30;
public PopularGamesViewModel(#NonNull Application application) {
super(application);
}
public LiveData<List<Game>> getGameList() {
final MutableLiveData<List<Game>> gameList = new MutableLiveData<>();
// Create the retrofit builder
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(igdbBaseUrl)
.addConverterFactory(GsonConverterFactory.create());
// Build retrofit
Retrofit retrofit = builder.build();
// Create the retrofit client
RetrofitClient client = retrofit.create(RetrofitClient.class);
Call<List<Game>> call = client.getGame(FIELDS,
ORDER,
LIMIT);
call.enqueue(new Callback<List<Game>>() {
#Override
public void onResponse(Call<List<Game>> call, Response<List<Game>> response) {
Timber.d("api call sucesss");
if (response.body() != null) {
Timber.d("First game: " + response.body().get(0).getName());
gameList.setValue(response.body());
}
}
#Override
public void onFailure(Call<List<Game>> call, Throwable t) {
Timber.d("api call failed");
}
});
return gameList;
}
}
When I run the code, the onResponse in the ViewModel class will output the correct response from the API call, so the call is working properly. But the onChanged() in the PopularGamesActivity class will never get called. Can someone shed some light on what I'm doing wrong? Thank you!
Ok, so this turned out to be a weird android studio bug. I was initially running the code on my real nexus 4 device, and the onChange never gets called. However, after running it on an emulated device, it started working immediately. And now, it's working on my real device too.
I don't know the actual reason behind it, but if anyone in the future run into a problem where onChange won't get called, try switching device/emulators.
Cheers.
I debug the code on a device and have the same problem.
M'n solution was simply activate the device screen.
The screensaver was the problem..

Pushing data from one user to another in Vaadin web app

I get the fact that it might take more than 10 lines of code (hopefully not more than 50), but I was wondering if you could help me anyway.
I'm trying to update one user's UI thread at runtime, based on another user's input. I've created a basic project which implements three predefined users (jim, tom and threeskin). I'd like to send a message from jim to tom and have it appear as a new Label object in tom's UI, without threeskin ever knowing about it, even though they're all logged in. Oh, and jim shouldn't have to refresh his page. The label should just spawn on screen out of it's own accord.
To say that I'd appreciate some help would be the understatement of the decade.
public class User {
public String nume;
public User(String nume) {
super();
this.nume = nume;
}
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
public class Engine implements ServletContextListener {
public static ArrayList<User>userbase;
public void contextDestroyed(ServletContextEvent arg0) { }
public void contextInitialized(ServletContextEvent arg0) {
System.out.println("This code is running at startup");
userbase =new ArrayList<User>();
userbase.add(new User("jim"));userbase.add(new User("tom"));userbase.add(new User("threeskin"));
}
}
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
public class InfigeUI extends UI {
User us3r;
#WebServlet(value = "/*", asyncSupported = true)
#VaadinServletConfiguration(productionMode = false, ui = InfigeUI.class)
public static class Servlet extends VaadinServlet {}
protected void init(VaadinRequest request) {
VerticalLayout everything=new VerticalLayout();
setContent(everything);
if (us3r==null){everything.addComponent(auth());}else{everything.addComponent(main());}
}
ComponentContainer auth(){
final VerticalLayout layout = new VerticalLayout();
layout.setMargin(true);
TextField userField=new TextField();
Button login = new Button("Log in");
login.addClickListener(new Button.ClickListener() {
public void buttonClick(ClickEvent event) {
us3r=login(userField.getValue());
if (us3r!=null){
saveValue(InfigeUI.this, us3r);
layout.removeAllComponents();
layout.addComponent(main());
}else{Notification.show("I only know jim, tom and threeskin. Which one are you?");}}
});
layout.addComponent(userField);
layout.addComponent(login);
return layout;
}
User login(String nume){
for (int i=0;i<Engine.userbase.size();i++){
if (nume.equals(Engine.userbase.get(i).nume)){return Engine.userbase.get(i);}
}
return null;
}
static void saveValue(InfigeUI ui,User value){
ui.us3r=value;
ui.getSession().setAttribute("something", value);
VaadinService.getCurrentRequest().getWrappedSession().setAttribute("something", value);
}
ComponentContainer main(){
VerticalLayout vl=new VerticalLayout();
Label label=new Label("This is the post-login screen");
String name=new String(us3r.nume);
Label eticheta=new Label(name);
TextField to=new TextField("Send to");
TextField message=new TextField("Message");
Button sendNow=new Button("Send now!");
vl.addComponent(eticheta);
vl.addComponent(label);
vl.addComponent(eticheta);
vl.addComponent(to);
vl.addComponent(message);
vl.addComponent(sendNow);
return vl ;
}
}
Basically you want three things
UI updates for a user which does no action himself, or in other words a message sent from the server to the browser. To enable this, you need to annotate the UI class using #Push. Otherwise, the update will only be shown when the user does something which causes a server visit, e.g. clicks a button
Some way of sending messages between UI instances (there is one UI instance per user). You can use some message bus implementation for this (CDI, Spring, ...) or you can make a simple on using a static field (static fields are shared between all users). See e.g. https://github.com/Artur-/SimpleChat for one way of doing it. It's also a good idea here to avoid all *.getCurrent methods as they in many cases will refer to another UI than you think (e.g. sender when you are in the receiver code), and you will do something else than you intend.
Safely update a UI when a message arrives. This is done using UI.access, also visible in the chat example.
First of all you need to enable the server push on your project help
based on Vaadin Documentation.
However, below code example will give what you want:
Create an Broadcast Listener Interface:
public interface BroadcastListener {
public void receiveBroadcast(final String message);
}
The Broadcaster Class:
public class Broadcaster {
private static final List<BroadcastListener> listeners = new CopyOnWriteArrayList<BroadcastListener>();
public static void register(BroadcastListener listener) {
listeners.add(listener);
}
public static void unregister(BroadcastListener listener) {
listeners.remove(listener);
}
public static void broadcast(final String message) {
for (BroadcastListener listener : listeners) {
listener.receiveBroadcast(message);
}
}
}
Your UI with Push Enalbed (via Annotation):
#Push
public class BroadcasterUI extends UI implements BroadcastListener {
#Override
protected void init(VaadinRequest request) {
final VerticalLayout layout = new VerticalLayout();
layout.setMargin(true);
setContent(layout);
final TextArea message = new TextArea("",
"The system is going down for maintenance in 10 minutes");
layout.addComponent(message);
final Button button = new Button("Broadcast");
layout.addComponent(button);
button.addClickListener(new Button.ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
Broadcaster.broadcast(message.getValue());
}
});
// Register broadcast listener
Broadcaster.register(this);
}
#Override
public void detach() {
Broadcaster.unregister(this);
super.detach();
}
#Override
public void receiveBroadcast(final String message) {
access(new Runnable() {
#Override
public void run() {
Notification n = new Notification("Message received",
message, Type.TRAY_NOTIFICATION);
n.show(getPage());
}
});
}
you can find the full link here.

Is there any way i can get the current Display object from a background thread(jobs) in RAP

we need to get the the current display object in RAP 2.3 from inside a job for updating the UI. what is the suggested way to do that?
The Threads in RAP articles gives a thorough explanation about how threads and sessions interrelate in RAP.
To gain access to the Display from a Job, the Job needs to know which Display it is assigned to. Hence you need to pass the Displya to the Job.
If the Job is scheduled from the UI thread, typical code may look like this:
static class DisplayJob extends Job {
private final Display display;
private DisplayJob( Display display ) {
super( "Job with UI Access" );
this.display = display;
}
#Override
protected IStatus run( IProgressMonitor monitor ) {
display.asyncExec( new Runnable() {
#Override
public void run() {
}
} );
return Status.OK_STATUS;
}
}
Button button = new Button( ...
button.addListener( SWT.Selection, new Listener() {
#Override
public void handleEvent( Event event ) {
new DisplayJob( event.display ).schedule();;
}
} );
Don't forget to check if the widgets aren't disposed before accessing them in the run() method given to asyncExec() - or use a helper therefore.
Note that the thread/session relation isn't specific to RAP but applies to all multi-user environments that have the concept of a session.

Gwt Logging into Client UI from Server-side

I have created GWT app, in which I have a Vertical Panel where I log the details.
Client side logging I'm doing using logger
sample code is:
public static VerticalPanel customLogArea = new VerticalPanel();
public static Logger rootLogger = Logger.getLogger("");
logerPanel.setTitle("Log");
scrollPanel.add(customLogArea);
logerPanel.add(scrollPanel);
if (LogConfiguration.loggingIsEnabled()) {
rootLogger.addHandler(new HasWidgetsLogHandler(customLogArea));
}
And I'm updating my vertical log panel using this code
rootLogger.log(Level.INFO,
"Already Present in Process Workspace\n");
But now my question is , I have to log server side details also into my vertical log panel.
My serverside GreetingServiceImpl code is:
public boolean createDirectory(String fileName)
throws IllegalArgumentException {
Boolean result = false;
try {
rootLogger.log(Level.INFO,
"I want to log this to my UI vertical log Panel");
system.out.println("log this to UI");
File dir = new File("D:/GenomeSamples/" + fileName);
if (!dir.exists()) {
result = dir.mkdir();
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
Now I want to log sysoutprt statements to my UI from here. How can I achieve this. Now using rootLogger.log(Level.INFO,
"I want to log this to my UI vertical log Panel"); code it is logging this to eclipse console . But how to log this to my UI in client side.
Please let me know If anything wrong in this question.
If I understood you right, you want to see your server log entries in web interface. And of course, java logger and printStackTrace() won't help you in that: your gwt code is compiled to JavaScript and has nothing to do with console and log files. Besides, your server can't "push" log entries to client - it's up to client to make requests. So if you want to track new log entries and move it to client, you need to poll server for new entries. And yet another problem: you may have many clients polling your servlet and you should keep in mind this multi-threading.
This is how I see probable implementation (it's just concept, may contain some errors and misspellings):
Remote interface:
public interface GreetingService extends RemoteService {
List<String> getLogEntries();
boolean createDirectory(String fileName)throws IllegalArgumentException;
}
Remote Servlet:
public class GreetingServiceImpl extends RemoteServiceServlet implements GreetingService {
public static final String LOG_ENTRIES = "LogEntries";
public List<String> getLogEntries() {
List<String> entries = getEntriesFromSession();
List<String>copy = new ArrayList<String>(entries.size());
copy.addAll(entries);
//prevent loading the same entries twice
entries.clear();
return copy;
}
public boolean createDirectory(String fileName)throws IllegalArgumentException {
Boolean result = false;
try {
log("I want to log this to my UI vertical log Panel");
log("log this to UI");
File dir = new File("D:/GenomeSamples/" + fileName);
if (!dir.exists()) {
result = dir.mkdir();
}
} catch (Exception e) {
log("Exception occurred: " + e.getMessage());
}
return result;
}
private List<String> getEntriesFromSession() {
HttpSession session= getThreadLocalRequest().getSession();
List<String>entries = (List<String>)session.getAttribute(LOG_ENTRIES);
if (entries == null) {
entries = new ArrayList<String>();
session.setAttribute(LOG_ENTRIES,entries);
}
return entries;
}
private void log(String message) {
getEntriesFromSession().add(message);
}
Simple implementation of polling (gwt client-side):
Timer t = new Timer() {
#Override
public void run() {
greetingAsyncService.getLogEntries(new AsyncCallBack<List<String>>() {
void onSuccess(List<String>entries) {
//put entries to your vertical panel
}
void onFailure(Throwable caught){
//handle exceptions
}
});
}
};
// Schedule the timer to run once in second.
t.scheduleRepeating(1000);
greetingAsyncService.createDirectory(fileName, new AsyncCallBack<Void>(){
void onSuccess(List<String>entries) {
//no need to poll anymore
t.cancel();
}
void onFailure(Throwable caught){
//handle exceptions
}
});
}
As you can see, I have used session to keep log entries, because session is client-specific and so different clients will receive different logs. It's up to you to decide what to use - you may create your own Logger class that will track users itself and give appropriate logs to appropriate clients.
And also you may want to save level of your messages (INFO,ERROR etc.) and then display messages in different colors (red for ERROR, for instance). To do so, you need to save not List, but some your custom class.
You'd create a logging servlet that has the same methods as your logging framework to send log messages to your server via RPC.
Here are some sample RPC log methods you can use:
public interface LogService extends RemoteService {
public void logException(String logger, String priority, String message, String error, StackTraceElement[] stackTrace, String nativeStack);
}
public interface LogServiceAsync {
public void logException(String logger, String priority, String message, String error, StackTraceElement[] stackTrace, String nativeStack, AsyncCallback<Void> callback);
}
public class LogServiceImpl extends RemoteServiceServlet implements LogService {
public void logException(String loggerName, String priority, String logMessage, String errorMessage, StackTraceElement[] stackTrace, String nativeStack) {
Logger logger = getLogger(loggerName);
Level level = getLevel(priority);
// Create a Throwable to log
Throwable caught = new Throwable();
if (errorMessage != null && stackTrace != null) {
caught = new Throwable(errorMessage);
caught.setStackTrace(stackTrace);
}
//do stuff with the other passed arguments (optional)
logger.log(level, message, caught);
}
}
Although those implementations are very nice, forget about timers and repeated server queries. We've something better now.
It's possible to push data from server to client using Atmosphere which supports WebSockets.

How can I correctly update a progress bar for an operation of unknown duration within an Eclipse wizard?

I have implemented a wizard for my Eclipse plug-in, showing several pages. One of these pages needs some lengthy initialization, that means it consists of a SWT table, which needs to be populated by information coming from an external source. This source needs to be activated first (one single method call that returns after a couple of seconds - I can not know in advance how long it will take exactly), before it can be used as input for for the table viewer. This initialization is currently done by the table model provider when it needs to access the external source for the first time.
Therefore, when I enter the wizard page, I would like to show a dummy progress bar that just counts up for a while. My approach was the following, but unfortunately does not work at all:
private void initViewer() {
IRunnableWithProgress runnable = new IRunnableWithProgress() { // needed to embed long running operation into the wizard page
#Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
SubMonitor progress = SubMonitor.convert(monitor);
Thread thread = new Thread() {
#Override
public void run() {
Display.getDefault().syncExec(new Runnable() {
public void run() {
viewer.setInput(ResourcesPlugin.getWorkspace().getRoot()); // this will make the table provider initialize the external source.
}
});
}
};
thread.start();
while(thread.isAlive()) {
progress.setWorkRemaining(10000);
progress.worked(1);
}
progress.done();
}
};
try {
getContainer().run(false, false, runnable);
} catch(Exception e) {
throw new Exception("Could not access data store", e);
}
}
This method gets then invoked when the wizard page's setVisible()-method is called and should, after a couple of seconds, set the viewer's input. This, however, never happens, because the inner-most run()-method never gets executed.
Any hints on how to deal with long-running (where an exact estimate is not available) initializations in Eclipse wizards would be very appreciated!
I have given below a simple example on how to use IRunnableWithProgress along with a ProgressMonitorDialog to perform a task of unknown quantity. To start with, have an implementation to IRunnableWithProgress from where the actual task is performed. This implementation could be an inner class.
public class MyRunnableWithProgress implements IRunnableWithProgress {
private String _fileName;
public MyRunnableWithProgress(String fileName) {
_fileName = fileName;
}
#Override
public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
int totalUnitsOfWork = IProgressMonitor.UNKNOWN;
monitor.beginTask("Performing read. Please wait...", totalUnitsOfWork);
performRead(_fileName, monitor); // This only performs the tasks
monitor.done();
}
}
Now, a generic implementation to ProgressMonitorDialog can be created as below which could be used for other places where a progress monitor dialog is required.
public class MyProgressMonitorDialog extends ProgressMonitorDialog {
private boolean cancellable;
public MyProgressMonitorDialog(Shell parent, boolean cancellable) {
super(parent);
this.cancellable = cancellable;
}
#Override
public Composite createDialogArea(Composite parent) {
Composite container = (Composite) super.createDialogArea(parent);
setCancelable(cancellable);
return container;
}
}
Having got the required implementation, the task can be invoked as below to get it processed with a progress dialog.
boolean cancellable = false;
IRunnableWithProgress myRunnable = new MyRunnableWithProgress(receivedFileName);
ProgressMonitorDialog progressMonitorDialog = new MyProgressMonitorDialog(getShell(), cancellable);
try {
progressMonitorDialog.run(true, true, myRunnable);
} catch (InvocationTargetException e) {
// Catch in your best way
throw new RuntimeException(e);
} catch (InterruptedException e) {
//Catch in your best way
Thread.currentThread().interrupt();
}
Hope this helps!
I assume the reason why it's "not working" for you is that the preparation of input is done in UI thread meaning that the progress bar cannot be updated. A better approach is to prepare input in advance and only set input to viewer after that.