I have an Android app that contains a users list with an Avatar for each user. The avatar image file is stored as a local .png file in the apps cache folder. From time to time, another service updates the avatar png files with more current ones (but in no regular order), and I would like to have my list of avatar ImageView update with the new .png files as they are saved to disk.
I have tried subclassing ImageView and adding a FileObserver property to it, however, this isn't seeming to be the most effective.
Does anyone have any recommendations on how to "live" bind a .png to an ImageView so that it will updates if/when the image file on disk changes?
I'm not sure if I should be looking into DataBinding or not because this seems overkill to me.
You can register BroadcastReceiver at activity and receive notifications from your service.
Firstly, you need to create an implementation of BroadcastReceiver like this:
public class YourBroadcastReceiver extends BroadcastReceiver {
/**
* Listener interface for received broadcast messages.
*/
public interface YourBroadcastListener {
void receivedBroadcast();
}
/**
* The Intent action that this Receiver should filter for.
*/
public static final String ACTION = "com.your.package.IMAGES_UPDATED";
private final YourBroadcastListener mListener;
public YourBroadcastReceiver(YourBroadcastListener listener) {
mListener = listener;
}
#Override
public void onReceive(Context context, Intent intent) {
if (mListener != null)
mListener.receivedBroadcast();
}
}
Next, you need to register YourBroadcastReceiver in your activity:
public class MainActivity extends Activity implements YourBroadcastListener {
private YourBroadcastReceiver mReceiver;
#Override
protected void onResume() {
super.onResume();
if(mReceiver == null){
mReceiver = new YourBroadcastReceiver(this);
registerReceiver(mReceiver, new IntentFilter(YourBroadcastReceiver.ACTION));
}
}
#Override
protected void onPause() {
super.onPause();
if(mReceiver != null){
unregisterReceiver(mReceiver);
mReceiver = null;
}
}
#Override
public void receivedBroadcast() {
// Received a broadcast notification that the images has changed - reload it
}
}
And the last, your service need to send brodcast notifications to your activity:
Intent i = new Intent("com.your.package.IMAGES_UPDATED");
sendBroadcast(i);
Take a note: your action string need to be unique to avoid collisions with another applications.
Related
In my MainActivity i can take Location with code like this:
private BroadcastReceiver broadcastReceiver;
private Location currentLocation;
#Override
protected void onResume(){
super.onResume();
if(broadcastReceiver == null){
broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
currentLocation = (Location)intent.getExtras().get("coordinates");
}
};
}
registerReceiver(broadcastReceiver, new IntentFilter("location_update"));
}
Now i have WebView in this Activity and want to send Location to JavaScript inside WebActivity.
To send something from Java code to JS code inside WebActivity i crate webinterface class and use code like:
#JavascriptInterface
public String GetHi(){
return "Hi";
}
But how can i send Location?
You can always use the good old Json to pass the data to the Javascript side (The Dark Side henceforth). Serialize your data and pass it as a serialized JSON string to your Dark Side side and then in the Dark Side you can deserialize it into your Javascript Location object.
public class MainActivity {
private MyListViewHelper mTimelineHelper;
#Override
protected void onCreate(Bundle savedInstanceState) {
mListViewHelper = new MyListViewHelper();
mListViewHelper.createListView();
startService(new Intent(this, MyService.class));
}
}
MyService class:
public class MyService extends Service {
private int mCount = 0;
public static final long NOTIFY_INTERVAL = 10 * 1000;
private static MyListViewHelper mListViewHelper;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
// cancel if already existed
if (mTimer != null) {
mTimer.cancel();
} else {
// recreate new
mTimer = new Timer();
}
// schedule task
mTimer.scheduleAtFixedRate(new TimeDisplayTimerTask(), 0, NOTIFY_INTERVAL);
}
#Override
public void onDestroy() {
mTimer.cancel();
}
private class TimeDisplayTimerTask extends TimerTask implements LocationListener {
#Override
public void run() {
mHandler.post(new Runnable() {
#Override
public void run() {
//DO SOMETHING HERE
...........
mCount++;
if(mCount==4){
mListViewHelper = new MyListViewHelper()
mListViewHelper.addItemToList("ABCD");
}
}
});
public boolean isMainActivityRunning(string packageName) {
ActivityManager activityManager = (ActivityManager)getSystemService (Context.ACTIVITY_SERVICE);
List<RunningTaskInfo> tasksInfo = activityManager.getRunningTasks(Integer.MAX_VALUE);
for (int i = 0; i < tasksInfo.size(); i++) {
if(tasksInfo.get(i).baseActivity.getPackageName.toString().equals(packageName)
return true;
}
return false;
}
}
}
MyListViewHelper class:
public class MyListViewHelper {
private ListView mListView;
private ArrayList<String> mArrayList;
public MyListViewHelper () {
}
public void createListView(){
mArrayList = new ArrayList<String>();
mListView = (ListView) activity.findViewById(R.id.listView1);
// I make a short version,so we suppose adapter is already prepared here
mListView.setadapter(adapter);
}
public void addItemToList(String myString){
mArrayList.add(myString);
adapter.notifiDateSetChanged();
}
}
What I want is for every 10 seconds, service will do something, and then if it do that 4 times, I will add one item to ListView. I forgot to save logcat before change it back to the time before I edited, so there is no logcat, sorry for that. But I'm pretty sure there is nothing wrong with others and the problem is just somewhere in the code above since I'm leanring Android and I don't have much knowledge about Service. Please teach me to fix that!
You could use EventBus to tell your Activity that the Service is updating the list.
For example...
Your Activity:
public void onEventMainThread(ListUpdateEvent event) {
mList.add(event.getValue());
mAdapter.notifyDataSetChanged();
}
Your Service:
EventBus.getDefault().post(new ListUpdateEvent("Value to add"));
ListUpdateEvent is a simple POJO that lets you share data. Using onEventMainThread allows you to automatically protect the list from being updated on a background thread.
Here is info about how to send messages from services to activity components. So you just need to send data about new list item.
Have you tried changing adapter.notifiDateSetChanged(); to adapter.notifyDataSetChanged();
And by the way, the best way to update your activity via service is to register a broadcast receiver in your activity's onResume() and send a broadcast from your service when you want to update the activity and put your data as an extra. Don't forget to unregister() your receiver in onPause() and to declare your service in the Manifest.
You can read more about data flow from service via broadcast to activity here http://www.truiton.com/2014/09/android-service-broadcastreceiver-example/
You can't. It is not possible to update UI components in an Activity from a Service. Or, if you'd find a way to do that, I would say it is a bad idea*.
I think the EventBus implementation of Knosses is a good idea. I would like to suggest an implementation using a database, Loaders and Broadcasts.
The Activity with the ListView would use a CursorLoader to get the list content from the data source. The Service would add the items to the database each NOTIFY_INTERVAL and send out a Broadcast. The Activity would register a BroadcastReceiver that listens to the Broadcasts send by the Service, on receiving a Broadcast in the Activity, call getLoaderManager().restartLoader(), so the content in the ListView will refresh.
Edit:
* Let me elaborate a bit on that; as you can also bind a Service to your Activity. In that case, it is perfectly valid to update a UI component through the binding.
I started an activity with the following code from my service:
Intent cmActivityIntent = new Intent(getApplicationContext(), CallManagementActivity.class);
cmActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(cmActivityIntent);
In the app's manifest the launchMode of the activity is defined as android:launchMode="singleTask".
I've been looking for ways of stopping the activity on the service's onDestroy() but I haven't been able to find any documentation that can help me. And I've only found one way of doing it.
I've seen the aproach of checking on the serviceif the activity is instanced using an activity-class-static-property (public static boolean isInstanced) and sending another intent to the activity with a FINISH_ACTIVITY extra if it is. Then, on the activity's onNewIntent() the flag would be checked and the activity would be finished if the flag was included.
This approach doesn't feel right to me, since intents are supposed to be used start activities and not stopping them.
Does someone know any other way of how to acomplish this?
Many alternatives have been given to solve this issue:
Use an Intent and set an Extra.
Send a broadcast intent from the Service and register a BroadcastReceiver on the Activity.
Use the LocalBroadcastManager (similar to previous alternative)
Bind the Activity to the Service and pass it a Messenger so the Service can use it to communicate with the Activity.
None of them have I found a good solution, in fact, since the service is only accesible from the package, the solution I'm sticking with is the Observer Pattern.
Next you'll find the code I used. The activities implement the Observer interface (my own) and the service the abstract class ObservableService.
ObservableService:
public abstract class ObservableService extends Service {
#Override
public void onCreate() {
super.onCreate();
serviceStarted = true;
observers = new ArrayList<Observer>();
}
#Override
public void onDestroy() {
super.onDestroy();
serviceStarted = false;
}
//---------------------------------------------------------
//
// Singleton methods
//
//---------------------------------------------------------
/**
* Flag set to <code>true</code> if the service is running and
* <code>false</code> if not.
*/
private static boolean serviceStarted;
/**
* Used to check if the service is started.
* #return <code>true</code> if the service is running and <code>false</code>
* if not.
*/
public static boolean isServiceStarted() {
return serviceStarted;
}
//---------------------------------------------------------
//
// Observable methods an constants
//
//---------------------------------------------------------
/**
* Used to notify the observers that the service has been stopped.
*/
public static String UPDATE_TYPE_SERVICE_STOPPED = "service_stopped";
protected static ArrayList<Observer> observers;
public static void addObserver(Observer observer) {
observers.add(observer);
}
public static void removeObserver(Observer observer) {
observers.remove(observer);
}
protected static void notifyObservers(String updateType) {
for (Observer observer : observers) {
observer.onObservableUpdate(updateType);
}
}
}
Observer:
public interface Observer {
public void onObservableUpdate(String updateType);
}
The activities just register themselves as observers at onResume() and unregister at onPause(). The isServiceStarted() is used to finish the activity if the service is already stopped.
#Override
protected void onResume() {
super.onResume();
if (InCallService.isServiceStarted()) {
InCallService.addObserver(this);
} else {
this.finish();
}
}
#Override
protected void onPause() {
super.onPause();
InCallService.removeObserver(this);
}
public void onObservableUpdate(String updateType) {
if (updateType.equals(ObservableService.UPDATE_TYPE_SERVICE_STOPPED)) {
this.finish();
}
}
I chose this because I found it to be a very simple and high-performance alternative.
I have a custom title bar used in every activity in my app. I have a broadcast receiver that I want manipulating said title bar,
I have this in my custom application class,
public class MyApplication extends Application {
public static final String ACTION_RESPONSE = "org.company.com.action.MESSAGE_PROCESSED";
private final BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
MyApplication.log("received");
//hide the visible progress dialog spinner in title bar
}
};
public BroadcastReceiver getBroadcastReceiver() {
return receiver;
}
}
I have this in every one of my Activities' onCreate,
MyApplication application = ((MyApplication) getApplication());
registerReceiver(application.getBroadcastReceiver(), new IntentFilter(MyApplication.ACTION_RESPONSE));
What I am trying to do is to find a nice OOP way to do this. I realize that in every activity I can bind a broadcastreceiver that manipulates the view as long as the braodcastreceiver is declared in the activity, but copy pasting that same code 15+ times doesn't make sense to me. Then the next issue is that inside of the MyApplication class, I don't have access to any of the views. Has anyone experienced this issue?
I currently have a tabhost with 5 tabs. Over one of the tabs I have an ImageView that when the tabs are created it pulls data via POST to display a number. I am wondering how from one of the tab activities (say Rate.java) I could call that method to update that ImageView that is over one of the tabs.
I know it's not very specific but I think I wrote it so you know what I am talking about.
Let me know if you require anymore info.
talitore
Based on the information given, two options that immediately come to mind are:
Send a broadcast from the tab activity (e.g. Rate.java) and have the activity hosting the ImageView listen for it.
Create some sort of BaseActivity (extending Activity) that takes a custom Listener interface with an update method. Have your tab activities extend that BaseActivity and the activity with your ImageView implement it. You can then call the update method on the listener from your tab activities (instantiate them as a BaseActivity and pass along the listener) and make the activity with the ImageView act upon it.
//Edit per request:
A good starting point for information about broadcasts and receivers is the documentation for the BroadcastReceiver. In your case it's probably easiest to just create them in code.
A minimal example will contain something like the following:
BroadcastSendingActivity:
public class BroadcastSendingActivity extends Activity {
public static final String UPDATE_IMAGEVIEW = "UPDATE_IMAGEVIEW";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sender);
Intent i = new Intent();
i.setAction(UPDATE_IMAGEVIEW);
sendBroadcast(i);
}
}
BroadcastReceivingActivity:
public class BroadcastReceivingActivity extends Activity {
private BroadcastReceiver mReceiver;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.receiver);
}
#Override
protected void onPause() {
super.onPause();
unregisterReceiver();
}
#Override
protected void onResume() {
super.onResume();
registerReceiver();
}
private void registerReceiver() {
if (mReceiver == null) {
mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(BroadcastSendingActivity.UPDATE_IMAGEVIEW)) {
// code to update imageview...
}
}
};
}
getApplicationContext().registerReceiver(mReceiver, new IntentFilter(BroadcastSendingActivity.UPDATE_IMAGEVIEW));
}
private void unregisterReceiver() {
if (mReceiver != null) {
getApplicationContext().unregisterReceiver(mReceiver);
}
}
}
Note that I did not test the code, but I'm sure you'll be able to figure out any mistakes I might've made. :)