Edit: here's the source on PasteBin. I feel like I might need to just redesign the entire Service.. :(
I'm the developer of RingPack. The basic idea is that there is a Service launched in the background that takes care of switching the ringtone out for the user. I'm having issues with losing my reference to an ArrayList within the Service. I think I may be misunderstanding how the lifecycle works. My intent was for it to be started whenever the user selects a pack from the Activity.
Intent i = new Intent(RingActivity.this, RingService.class);
i.putExtra(RingService.ACTION, RingService.PACK_SET);
i.putExtra(RingService.PASSED_PACK, currentPackId);
RingActivity.this.startService(i);
I tell the Service to set the Default Notification Tone to the first tone of the pack corresponding to "currentPackId".
When the user wants to turn off RingPack, the disabling is done like so:
Intent i = new Intent(RingActivity.this, RingService.class);
RingActivity.this.stopService(i);
Toast.makeText(RingActivity.this.getBaseContext(), RingActivity.this.getString(R.string.ringPackDisabled), Toast.LENGTH_SHORT).show();
So the Service's onCreate looks like so:
public void onCreate() {
db = new DbManager(this);
db.open();
vib = (Vibrator) getSystemService(VIBRATOR_SERVICE);
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_TIME_TICK);
registerReceiver(tReceiver, intentFilter);
timeTick = 0;
//figure out the widget's status
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
widgetEnabled = prefs.getBoolean(WIDGET_ALIVE, false);
//save the ringtone for playing for the widget
Uri u = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
r = RingtoneManager.getRingtone(this.getBaseContext(), u);
playAfterSet = false;
super.onCreate();}
Then it passes it off to onStartCommand, which returns START_NOT_STICKY (since I will be creating and destroying the Service manually), who passes it off to handleStart().
#Override private void handleStart(Intent intent) {
final Intent i = intent;
if (isSdOk()) {
int action = i.getIntExtra(ACTION, -1);
if (action != -1) {
if (action == PACK_SET) {
playAfterSet = true;
Thread packSetThread = new Thread() {
#Override
public void run() {
int passedPackId = i.getIntExtra(PASSED_PACK, -1);
//we were passed the id
if (passedPackId != -1) {
checkPrefs();
if (!enabled)
initControl(passedPackId);
else
setPack(passedPackId);
packSetHandler.sendEmptyMessage(0);
}
}
};
packSetThread.start();
}
else if (action == NEXT_TONE) {
checkPrefs();
swapTone();
}
else if (action == PLAY_TONE) {
playCurrentTone();
}
else if (action == WIDGET_STATUS) {
widgetEnabled = intent.getBooleanExtra(WIDGET_ALIVE, false);
if (toneName != null)
RingWidget.update(getBaseContext(), toneName);
}
}
}}
The isSdOk() method just checks if the SD card is mounted, since the ringtones are stored on it. initControl() just saves the user's default ringtone, so that we can give it back when they disable us. The setPack() method looks like this:
private void setPack(int packId) {
//save the current pack id in the prefs for restarting
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(RingService.this.getBaseContext());
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(SAVED_PACKID, packId);
editor.commit();
//get the info we need to work with this pack
//it's path on the SD
//build the tones ArrayList to work from
grabPath(packId);
if (tones == null)
tones = new ArrayList<Integer>();
else
tones.clear();
mapTones(packId);
currIndex = 0;
setNotificationTone(tones.get(currIndex));}
The tones ArrayList is what I've been losing. This is where it is initialized. It holds the ids of all the enabled ringtones within a pack. The NullPointerException I've been seeing is in swapTone():
private void swapTone() {
//locked
if (lockPref)
return;
//shuffle on
else if (shufflePref) {
int randIndex = currIndex;
while (randIndex == currIndex)
randIndex = (int) Math.floor(Math.random() * tones.size());
currIndex = randIndex;
}
//shuffle off
else {
if (currIndex != (tones.size() - 1))
currIndex++;
else
currIndex = 0;
}
setNotificationTone(tones.get(currIndex));}
They way I intended it work is for swapTone() to never be called if setPack() hasn't already. Again, my users keep getting this error, but I can't reproduce it myself. Any help would be greatly appreciated. I apologize for the code wall, but I am very confused. Perhaps I'm not using the concept of a Service correctly?
Well, despite the "code wall", your listings are incomplete (e.g., you say your problem is with tones but never show where it is defined). I am going to take a guess that it is a data member of your Service.
In that case, it will be null if the service was stopped between PACK_SET and NEXT_TONE operations. This can easily occur, if Android stops the service because it has been running too long. Even with START_NOT_STICKY, if the next startService() call after Android stops the service is NEXT_TONE, not PACK_SET, you will have this problem.
IOW, your data model (the chosen ring pack) is not stored in a persistent location, but rather is held in RAM (tones). You have two choices:
Try to figure out a way that you do not need to be an always-running service, and load the tones out of a persistent store (e.g., database) as needed. This would be ideal, as you are chewing up a bunch of RAM while not delivering value for that RAM every microsecond. For example, you could use AlarmManager with an IntentService.
Use startForeground() to make it less likely that Android will stop your service. The trade-off is that you will need to place a Notification on the screen, so the user knows you are constantly running. You might make that Notification lead the user to the activity where they can configure or shut down the service.
Related
I have an Activity and a Service.
In my Activity, a button interacts with the Service to start/stop GPS logging.
My Service has 3 state indicators: One for being connected to Google Play Services, one for actively logging GPS, and one for processing what was logged.
When connected to Google Play Services the Service flow is this:
Ready -> Logging -> Processing -> Ready
The Service will broadcast these states as follows:
private void UpdateStatusBroadcast() {
//Save status variables to intent
Intent intent = new Intent(this.getString(R.string.BroadcastStatusIntent));
intent.putExtra(getString(R.string.BroadcastIsConnected), mIsConnected);
intent.putExtra(getString(R.string.BroadcastIsTripActive), mIsTripActive);
intent.putExtra(getString(R.string.BroadcastIsProcessing), mIsProcessing);
//Send the broadcast
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
My Activity receives the states as follows:
private class StatusReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
mIsConnected = intent.getBooleanExtra(getString(R.string.BroadcastIsConnected), false);
mIsTripActive = intent.getBooleanExtra(getString(R.string.BroadcastIsTripActive), false);
mIsProcessing = intent.getBooleanExtra(getString(R.string.BroadcastIsProcessing), false);
HandleConnectionStatus();
HandleTripStatus();
}
}
Then comes my problem. In HandleTripStatus(), posted below, i change the text and background of a button to reflect what the Service is currently doing. This works fine for the first and the third case. I never see the second background drawn however, in spite of receiving the correct boolean values.
private void HandleTripStatus() {
Button tripButton = (Button) findViewById(R.id.TripButton);
Button liveMapButton = (Button) findViewById(R.id.LiveMapButton);
if (mIsTripActive) {
tripButton.setText(R.string.TripButtonTitleStop);
tripButton.setBackground(ContextCompat.getDrawable(mContext, R.drawable.trip_button_stop_shape));
liveMapButton.setEnabled(true);
} else if (mIsProcessing) {
tripButton.setText(R.string.TripButtonTitleStopping);
tripButton.setBackground(ContextCompat.getDrawable(mContext, R.drawable.trip_button_stopping_shape));
liveMapButton.setEnabled(false);
} else {
tripButton.setText(R.string.TripButtonTitleStart);
tripButton.setBackground(ContextCompat.getDrawable(mContext, R.drawable.trip_button_start_shape));
liveMapButton.setEnabled(false);
}
}
To debug the issue i verified the following:
Text and background resource is correctly defined (i.e. trying to use
it instead of the first and third case works)
The if-else conditions runs when expected (i.e. the "else if" condition actually runs when I expect it to. Verified by breakpoint.)
No other if-else condition is used in the process. (i.e, only the correct condition is run.)
Some other code that could possibly be relevant:
This is how the Activity requests that the GPS logging should stop (Leading to the processing step before finishing)
private void EndTrip() {
//Create message to TripService with intent to run case for END_TRIP
Message message = Message.obtain(null, TripService.END_TRIP, 0, 0);
//Send the Message to the Service
try {
mMessenger.send(message);
Toast.makeText(mContext, R.string.TripStopToast, Toast.LENGTH_SHORT).show();
} catch (RemoteException e) {
Log.e("Debug", "Failed to contact TripService");
}
}
This is the structure of what happens in the Service after receiving the message from the Activity.
private void EndTrip() {
//Stop retrieving location updates
//Broadcast the updated status and begin processing the trip
mIsTripActive = false;
mIsProcessing = true;
UpdateStatusBroadcast();
//Processing the collected data
//Finish up
mIsProcessing = false;
UpdateStatusBroadcast();
stopForeground(true);
}
I am all out of ideas. What can the cause be? Why does the button background not change in the else-if?
After too many hours of trial and error, I found the cause to be thread-related.
What I learned:
My service doing its work (Processing) would hang up the UI thread until done
This was quite simply because the service was running on the UI thread
Android does not automatically run services in a thread seperate from the rest of your application.
It is possible to run your service on a different thread. To do this, add the following to your AndroidManifest, inside your service:
android:process=":WhateverNameYouLikeForYourThread"
Note that this of course broke the broadcasts i relied on. This was however easy to fix; The consequence is that I can no longer use LocalBroadcastManager
By example - Instead of
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
i now use
sendBroadcast(intent);
instead. This does however mean that the broadcasts are less private.
How can I make a certain function execute after every 10 seconds for an infinite time?
What I have done till now: I am getting the location values of the user from the App and storing them on the server. I am using a service, so that, the code keeps running for an infinite time, I am using a Broadcast receiver, so that, if the phone is booted, the service should start again and starts sending me the location.
The issue Everything works perfectly fine for about first 10-15 minutes, but, after this, the service gets stopped by itself. Also, when the user signs up for the App, authorized tokens are generated. These tokens are also sent in the POST call as one of the parameters, for security purposes. Even these tokens are lost, despite working perfectly fine for the initial 10 minutes. I am storing these tokens in SharedPreferences. Any help in this regard would be highly appreciated.
Code for SharedPreferences
Log.i("onCreate", "onCreate");
Log.i("atoken value", ConfirmToken.avalue);
Log.i("utoken value", ConfirmToken.uvalue);
atoken = ConfirmToken.avalue;
utoken = ConfirmToken.uvalue;
Log.i("atoken value", atoken);
Log.i("utoken value", utoken);
Log.i("Starting SharedPref", "Starting SharedPref");
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putString("atoken", atoken);
editor.putString("utoken", utoken);
editor.commit();
if (settings.contains("atoken")) {
atoken = settings.getString("atoken", "nulll");
Log.i("Inside SharedPref", atoken);
}
if(settings.contains("utoken")) {
utoken = settings.getString("utoken", "nulll");
Log.i("Inside Sharedprefs", utoken);
}
//Calling UpdateData here so that values of lat, lng get updated, before they are used by MyService.java
UpdateData();
startService(new Intent(this, MyService.class));
Now, the update data function simple makes a POST call, using these tokens and Location values as parameters.
This is how I run a task every few seconds. Note that it runs in another thread, so accessing UI elements needs runOnUiThread call, but since you are in a service, you will not have any issues with that.
private ScheduledThreadPoolExecutor taskExecutor;
private void stopTimerTask() {
if (taskExecutor != null)
taskExecutor.shutdownNow();
// keep one task at any given time
taskExecutor = new ScheduledThreadPoolExecutor(1);
}
private void startTimerTask() {
stopTimerTask();
taskExecutor.scheduleWithFixedDelay(Timer_Tick, TIMER_INITIAL_DELAY, TIMER_PERIOD, TimeUnit.MILLISECONDS);
Log.d("Pool", "Timer Task Running");
}
private Runnable Timer_Tick = new Runnable() {
#Override
public void run() {
// Do something
}
};
You should however be aware that Android OS may terminate your service at any time when running low on resources or it feels like the service is doing too much work. You should start focusing on how to restore it's state, just like every one else does.
I think the following is the easiest way.
Also remember if you are making a network call, make an asynchronous request so that your app doesn't stop working while your app waits for the response.
for(long startTime = new Date().getTime();new Date().getTime() - startTime ==10000; startTime++)
{ /*your code goes here*/ };
No matter what you do, if the system is low on resources, it will terminate your app.Please let me know weather this works for you or not :)
I have an application that uses IntentService to run a background task where I pull data from a website, parse the data out, and create calendar events based on the results. Everything seems to be working create, except I'm running into an issue with rotation.
Using the code below, when I rotate the screen, the ProgressDialog box stays visible, but is never updated with new text when the process is updated, and never goes away once the call is completed. I'm using an IntentService instead of an ASyncTask because the user can also schedule the IntentService to run at other times without having to interface with the app. Any help is appreciated, thanks!
public void onCreate(Bundle savedInstanceState) {
Object retained = getLastNonConfigurationInstance();
if (retained instanceof CalendarHandler) {
// CH is a class level variable defined at the top which references my IntentService, aptly named CalendarHandler
ch = (CalendarHandler) retained;
ch.setActivity(this);
} else {
ch = null;
}
activity = this;
btnLogin.setOnClickListener(OnClickListener(View view) {
ch = new CalendarHandler();
ch.setActivity(MyTlc.this);
// Do other stuff, like run the intent service
}
}
public Handler handler = new Handler() {
public void handleMessage(Message message) {
// We read the information from the message and do something with it
// based on what the result code is
String result = message.getData().getString("status");
if (result.equals("ERROR")) {
activity.removeDialog(PROGRESS_DIALOG);
results.setText(message.getData().getString("error"));
} else if (result.equals("DONE")) {
activity.removeDialog(PROGRESS_DIALOG);
int count = message.getData().getInt("count", 0);
activity.results.setText("Added " + count + " shifts to the calendar");
} else {
activity.pDialog.setMessage(result);
}
super.handleMessage(message);
}
};
From what I understand, this should work, and like I said the ProgressDialog box does stay properly, I just can't seem to pass information to the dialog box after rotating.
I have a timer that counts up from the time a user encounters that activity
I am currently using a Chronometer set during onCreate (initially started only when certain conditions are met). But I need the chronometer to keep counting upward until the app and all its views are closed (I have an "Exit" function to do that).
The problem is that the Chronometer gets reset to zero on every time I look at another tab and come back to its activity. (This has to do with the oncreate, but I dont know the way around it)
I didn't find an intuitive way to save the chronometer's state or countup in the background on its own (or to perhaps keep track of the time on my own and update the chronometer visually at a different point in time)
One idea I had was to start the Chronometer with a service and let the service keep counting , while having a textview in the existing activity update using the chronometer's current time tally as a string
any insight on a known approach to this problem be appreciated!
This is further complicated because this is an activity in a tabhost, and tabhosts call both onPause and onResume every time you load a view, so this breaks lifecycle functions.
There are a number of ways to persist the time. The easiest one I have found is to store the time in the Intent that was used to create the original activity via getIntent().putExtra("START_TIME", floatvalue). You may retrieve the value with getIntent().getFloatExtra("START_TIME", 0f). Doing it this way has a number of benefits:
It doesn't break the Activity LifeCycle and does not require a Context.
It can be passed easily between other Activities and Applicaitons.
It persists among Pauses and Stops.
It doesn't require special listeners.
It doesn't create any new objects (the Intent is the one used to create the Activity the first time).
This solution is great for persisting in a Tabbed Activity, or across Dialogs, etc. It has some limitations if leaving the Application to a more memory intensive one, but only if your Activity is destroyed (due to memory).
Because of my Tabhost, the lifecycle functions could not be relied on.
What I did was make the chronometer a static global in a central class, and added a ontabchangedlistener within my tabhost that checked to see if the tab being changed to was the tab with the chronometer. If this was true then it stores the Long value of the chronometer's current time.
tabHost.setOnTabChangedListener(new OnTabChangeListener(){
#Override
public void onTabChanged(String arg0) {
// TODO Auto-generated method stub
if(arg0.contentEquals("homeGroup"))
{
//store time in centralhelper.java
//stopWatch is of type Chronometer
//stopWatchLastTime is of type Long and is initially set to zero. Chronometer uses milliseconds to determine time, will never be zero after set
CentralHelper.stopWatchLastTime = CentralHelper.stopWatch.getBase();
}
}
});
When my homeGroup view loads, the onResume() function is called, there is a condition here to retrieve the time for the chronometer to resume counting from. Despite the fact that a tabhost will call both onPause() and onResume() in EVERY load outside of normal lifecycle functions, they still get called before onCreate()
public void onResume(){
super.onResume();
//update Chronometer with time stored in tabchangelistener
if(CentralHelper.stopWatchLastTime!=0)
CentralHelper.stopWatch.setBase(CentralHelper.stopWatchLastTime);
}
this allowed me to do a similar check in onCreate()
if(CentralHelper.stopWatchLastTime!=0)
{
CentralHelper.stopWatch.start(); //this is where it resumes counting from the base set in onResume()
}
else
{
CentralHelper.stopWatch.start();
CentralHelper.stopWatch.setBase(SystemClock.elapsedRealtime());
}
When you switch to a different activity the previous one is paused (onPause, asand so on, in attached image) when you came back to the activity it is resumed, but occasionaly when dalvik runs out of memory your Activity object can be deleted when ton showing.
If you keep your application data in the Activity instance you might loose it accidentally, please read this Activity Lifecycle http://developer.android.com/reference/android/app/Activity.html
This approach is tested and it works really well.
Try this:
Take a boolean volatile variable which will control your thread(start/stop). Take three text views, hour, min and sec text views, and remove chronometer completely. Update your UI using a Handler Write the following code.
public void timeUpdate()
{
timerThread = new Thread(new Runnable() {
#Override
public void run() {
while(continueThread){
Date newDate = new Date();
if(((newDate.getTime()) - date.getTime()) > 1000){
secondCounter = secondCounter+1;
mHandlerUpdateSec.post(mUpdateSec);
System.out.println("Inside the Theread ..."+secondCounter);
if(secondCounter > 59){
minuteCounter = minuteCounter + 1;
mHandlerUpdateMinute.post(mUpdateMinute);
secondCounter = 0;
if(minuteCounter > 59){
hourCounter = hourCounter + 1;
mHandlerUpdateHour.post(mUpdateHour);
minuteCounter = 0;
}
}
}
try{
timerThread.sleep(1000);
}catch (Exception e) {
// TODO: handle exception
}
}
}
});
timerThread.start();
}
The continueThread is a boolean volatile variable. Setting it to false will stop the thread. The timerThread is an instance of thread. There are three counters, hour, min and sec counters which will give you the latest time values. The handlers are updated as follows.
final Handler mHandlerUpdateSec = new Handler();
final Runnable mUpdateSec = new Runnable() {
public void run() {
String temp = "" + secondCounter;
System.out.println("Temp second counter length: " + temp.length());
if(temp.length() == 1)
secTextView.setText("0" + secondCounter);
else
secTextView.setText("" + secondCounter);
}
};
final Handler mHandlerUpdateMinute = new Handler();
final Runnable mUpdateMinute= new Runnable() {
public void run() {
String temp = "" + minuteCounter;
System.out.println("Temp second counter length: " + temp.length());
if(temp.length() == 1)
minTextView.setText("0" + minuteCounter);
else
minTextView.setText("" + minuteCounter);
}
};
final Handler mHandlerUpdateHour = new Handler();
final Runnable mUpdateHour = new Runnable() {
public void run() {
String temp = "" + hourCounter;
System.out.println("Temp second counter length: " + temp.length());
if(temp.length() == 1)
hourTextView.setText("0" + hourCounter);
else
hourTextView.setText("" + hourCounter);
}
};
Now, whenever you want to start the timer, set continueThread to true and call timeUpdate(). To stop it, just do continueThread = false. To start the thread again, set continueThread to true and call timeUpdate() again. Make sure you update the counters accordingly while you start/stop the timer.
You could save the start time in a sharedpreferences (or file, etc.) and establish your count-up from that (rather than starting at 0) in onResume().
Your UI may need some changes to handle the fact that you will have to reset the start time, since it could theoretically count forever.
I am developing an app in Android that performs a background sync with a server (using SyncAdapter and authentication etc).
When the foreground app (with UI) is started, there maybe a background sync in progress, or optionally it may start one via a UI button.
I would like a way to "plug into" an on-going background sync (whether started by the system, or the periodic sync setting or the UI) and show it's progress in the foreground activity.
The ContentResolver documentation (http://developer.android.com/reference/android/content/ContentResolver.html) mentions a mysterious "SyncObserver" that has no link to javadoc and is not documented (that I can find).
There are some other pages around that mention it (http://www.chinaup.org/docs/migrating/m5-0.9/changes/android.content.ContentResolver.html) but I can't find out more about it.
Has anyone implemented this beast?
If not, does anyone have example code or recommendations on tracking the progress of a background sync in a foreground Activity?
Thanks for the answer.
Due to the async nature of the background sync your App (activity) could be started with a background sync already underway, which you detect with the status stored in a preference.
What I have done is to implement a SyncObserver class that implements the SyncStatusObserver interface and create/destroy on suspend/resume.
syncObserverHandle = ContentResolver.addStatusChangeListener( ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE, new SyncObserver() );
This gets informed of any event related to sync (pending, started, etc) and I also check for status using
ContentResolver.isSyncActive();
The Android APIs for this are pretty basic, and you have to respect rules about what is done on the UI thread and what is not, but if anyone want to see my implementation just post a question and point me to it and I will answer with pleasure.
I had this same problem and ended up implementing it with a combination of 1) a broadcast from the SyncAdapter, and 2) using SharedPreferences to indicate status.
In the SyncAdapter, something like this this:
public static final String START_SYNC = "com.whatever.sync.start";
public static final String STOP_SYNC = "com.whatever.sync.stop";
public static final String SYNC_PROGRESS = "syncProgress";
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
// Add an integer to the shared settings to indicate the status
SharedPreferences settings = mContext.getSharedPreferences(Constants.PREFS, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putInt(SyncAdapter.SYNC_PROGRESS, 0);
editor.commit();
Intent intent = new Intent();
intent.setAction(START_SYNC);
mContext.sendBroadcast(intent);
//... do some stuff, setting SYNC_PROGRESS to other values and
// sending more broadcasts as the state changes
// When we are done, remove the "in progress" setting and store some
// other data
editor.putString(SyncAdapter.LAST_UPDATED, new Date().toString());
editor.remove(SyncAdapter.SYNC_PROGRESS);
editor.commit();
Intent stopIntent = new Intent();
stopIntent.setAction(STOP_SYNC);
mContext.sendBroadcast(stopIntent);
}
In the activity we do two things at resume 1) check the shared preference for whether a sync is currently in progress, 2) register to listen for broadcasts with a receiver.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// .. do some UI stuff
mReceiver = new SyncReceiver(this);
}
#Override
public void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(SyncAdapter.START_SYNC);
intentFilter.addAction(SyncAdapter.STOP_SYNC);
registerReceiver(mReceiver, intentFilter);
showProgress();
}
public void showProgress() {
SharedPreferences settings = getSharedPreferences(Constants.PREFS, 0);
if (settings.contains(SyncAdapter.SYNC_PROGRESS)) {
// ... set the UI to show that a sync is in progress
} else {
// ... set the UI to show that a sync is NOT in progress
}
}
private class SyncReceiver extends BroadcastReceiver {
private MyActivity mActivity;
public SyncReceiver(MyActivity activity) {
mActivity = activity;
}
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(SyncAdapter.START_SYNC)) {
Log.i("#string/app_name", "Started sync");
mActivity.showProgress();
}
else if (intent.getAction().equals(SyncAdapter.STOP_SYNC)) {
Log.i("#string/app_name", "Started sync");
mActivity.showProgress();
}
}
}
This seems to work for me. I must admit I have a feeling that there are some potential issues with this due to the asynchronous nature of the broadcasts. Any input on improving my approach would be appreciated!