send results multiple times from IntentService - java

I have a IntentService that queries the MediaStore to get songs, albums, artist, whatnot. that way if someone tries to add an entire artist and there are a lot of albums the IntentService can do the expensive operations of getting all the albums from an artist, then looping through them to get all the songs from each album and it won't take up the ui so the app can still function. My problem/question is, is there a way to send results multiple times from the same service call?
Example: Someone clicks on an artist. That starts the IntentService with the artist_id and the IntentService starts doing its work. What I would like to have happen is, the IntentService gets the first album, then the first song and sends that result back, but it also continues to process the rest of the albums and songs, then when that is done it sends them all back. I don't know if that makes sense or not, so here is an example of my current setup...
BaseActivity...
public GetSongsReceiver receiver;
public GetSongsReceiver.Receiver returnReceiver = new GetSongsReceiver.Receiver() {
#Override
public void onReceiveResult(int resultCode, Bundle resultData) {
if(resultCode == RESULT_OK) {
ArrayList<Song> songs = resultData.getParcelableArrayList("songs");
if(songs != null && songs.size() > 0) {
if(musicBound) {
musicService.addAllSongs(songs);
musicService.playSong();
}
}
}
};
...
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
//processing and such.
if(musicBound) {
musicService.setPlayType(MusicService.PlayType.ARTIST);
Intent i = new Intent(BaseActivity.this, GetPlaylistItemsService.class);
Bundle extras = new Bundle();
extras.putParcelable("key", data.getParcelableExtra("artist"));
extras.putString("service_type", "artist");
extras.putParcelable("receiver", returnReceiver);
i.putExtras(extras);
startService(i);
}
}
GetPlaylistItemsService...
#Override
protected void onHandleIntent(Intent intent) {
ResultReceiver rec = intent.getParcelableExtra("receiver");
Artist artist = intent.getParcelableExtra("key");
ArrayList<Album> albums = getAlbumsFromArtist(artist);
ArrayList<Song> songs = new ArrayList<>();
for(Album album : albums) {
songs.addAll(getSongsFromAlbum(album);
}
Bundle extras = new Bundle();
extras.putParcelableArrayList("songs", songs);
rec.send(Activity.RESULT_OK, extras);
}
What I would like to be able to do is have the "rec.send..." send more than one time. That way I can get the first album and first song, send that result back so the media player can start playing it, then process the rest in the background and add them when they are finished. That would mean the IntentService would need to be able to rec.send more than once. Is that possible or do I need to break this into 2 different IntentService calls, one to get the first item and then another to get the rest?

it won't take up the ui so the app can still function
You do not need an IntentService for this. An ordinary background thread (perhaps tied to a LiveData), AsyncTask, RxJava chain, etc. can handle this.
That would mean the IntentService would need to be able to rec.send more than once. Is that possible
Sure. It would be more efficient to use an ordinary background thread, AsyncTask, RxJava chain, etc. But, you should be able to send() as many times as you like. The results will be handed to onReceiveResult() one at a time (i.e., you call send() 6 times, you get 6 onReceiveResult() calls).

Related

Editing and saving an ArrayList<> that has been passed to another activity

I am making a frisbee logger and have an ArrayList of Team objects. Each Team has an ArrayList of Player objects. Everything is using Serializable properly to be sent using Intent.
In my main activity I am displaying the list of Team objects in a ListView and an option to add another Team (only a name is needed). Once a Team is selected I pass the object to another activity using Intent. On this second activity I have it display the list of Player objects and have fields to enter another player object into the passed list.
When I return to the main activity and go back to the add Player activity, what I have added is gone.
I cannot use static because there is obviously more than one Team object. I think passing back the changed ArrayList could work but that seems a little lame, time-consuming, and frustrating.
Is there a built-in way in Android Studio that does this or am I on my own?
Note: I am not using SQLite as suggested in the comments
There's not a whole lot to show on this but here it is I guess:
MainActivity.java
private static ArrayList<Team> listOfTeams = new ArrayList<>();
private static ArrayList<Game> listOfGames = new ArrayList<>();
private ListView gameList, teamList;
.....
teamList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Team t = (Team)teamList.getItemAtPosition(position);
viewTeam(t);
}
});
.....
//Item select in teamList. Start the TeamViewActivity
public void viewTeam(Team t)
{
Intent i = new Intent(this, TeamViewActivity.class);
i.putExtra("teamView",t);
startActivity(i);
}
TeamViewActivity.java
private Team team;
private ListView rosterList;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_team_view);
rosterList = (ListView) findViewById(R.id.playerList);
Intent i = getIntent();
Bundle extras = i.getExtras();
if(extras!=null)
{
if(extras.get("teamView")!=null)
{
team = (Team) extras.get("teamView");
}
}
populateRosterList(team.getRoster());
}
public void addPlayerToRoster(View view)
{
String checkFirst = ((EditText) findViewById(R.id.firstText)).getText().toString();
String checkLast = ((EditText) findViewById(R.id.lastText)).getText().toString();
String checkNumber = ((EditText) findViewById(R.id.numberText)).getText().toString();
if(!checkNumber.equals(""))
{
team.addPlayer(checkFirst, checkLast, Integer.parseInt(checkNumber));
((EditText) findViewById(R.id.firstText)).setText("");
((EditText) findViewById(R.id.lastText)).setText("");
((EditText) findViewById(R.id.numberText)).setText("");
populateRosterList(team.getRoster());
}
}
public void returnToMain(View view)
{
Intent i = new Intent(this, MainActivity.class);
i.putExtra("teamView", team);
startActivity(i);
}
private void populateRosterList(ArrayList<Player> list)
{
ArrayAdapter<Player> adapter = new ArrayAdapter<>(this,
R.layout.activity_list, R.id.genericText, list);
rosterList.setAdapter(adapter);
}
Consider your concept:
You serialize an object, i.e. you transform it into a transferrable format which is then copied over to the other activity and reconstructed as a new instance.
Consequently, you alter another instance, which is not available in the previous activity, if you do not return it - again, serialized - and finally reconstruct and copy it back into the respective instance.
What you need is a shared memory storage in your application, which can alter and retrieve data cross-activity OR a proper data routing using Intents w/ ISerializable.
Options:
Always serialize objects and pass and copy them around.
-> No multithreaded alteration, possibly slow, unbeautiful
Singleton application with global data storage ir Context Object (I do NOT recommend the due to memory management and Garbage
Collection inbetween Activity Switches BUT for consistency I'd
wanted to mention this option)
SQLite3
-> Quick, Simple and Scalable, But a bit cumbersome to get started with
Any other file-structure stored and maintained in the data folder
-> I'd expect a lot of boilerplate code here, and low performance
Webservice and remote database
Proper component setup, i.e. initialize all accessing components in your software with the appropriate reference to the data structs using for example fragments (Thanks to #mismanc, I actually missed that option initially)
In general you could abstract all that away using services and repositories, which allows you to under-the-hood test options like 3. 4. And 5. and find your best solution, and in addition, keeo the accessing code simple and clean.
in your case, you can use startActivityForResult instead of startActivity, then get your modified Team object from onActivityResult(int requestCode, int resultCode, Intent data) to update your list.
startActivityForResult example
You can use fragments. You hold the list in the MainActivity and pass its reference to ShowListFragment and AddPlayerFragment by interfaces. And you can also do other operations over them. If you dont want to use json or sqlite it can be a good way for you.
MainActivity.java
public class MainActivity extends Activity implements ShowListener{
public interface ShowListener{
ArrayList<Team> getTeamList();
}
private ArrayList<Team> listOfTeams = new ArrayList<>();
#Override
public ArrayList<Team> getTeamList() {
return listOfTeams;
}
}
ShowListFragment.java
public class ShowListFragment extends Fragment {
private ArrayList<Team> listOfTeams;
private ShowListener listener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
listener = (ShowListener)getActivity();
listOfTeams = listener.getTeamList();
}
}
As #Kingfisher Phuoc mentioned you could use srartActivityForResult in case you don't want to change your approach.
Otherwise I will suggest you use either :
SharedPreference to store your arraylist object (by converting the arraylist to json then store it as string in json format). In the PlayerActivity you retrieve the data, manipulate it then save it. see this post
SQLite

Saved Games on Android: how to check if a snapshot with the same name already exists?

Currently I am working on the Google's Saved Games integration into an Android app.
I am trying to create a new snapshot after the user requests new save. I implemented onActivityResult as i found here:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
// requestCode and resultCode checks happen here, of course...
if (intent != null) {
if (intent.hasExtra(Snapshots.EXTRA_SNAPSHOT_METADATA)) {
// Load a snapshot.
SnapshotMetadata snapshotMetadata = intent.getParcelableExtra(Snapshots.EXTRA_SNAPSHOT_METADATA);
currentSaveName = snapshotMetadata.getUniqueName();
loadFromSnapshot(snapshotMetadata);
} else if (intent.hasExtra(Snapshots.EXTRA_SNAPSHOT_NEW)) {
// Create a new snapshot named with a unique string
// TODO: check for existing snapshot, for now, add garbage text.
String unique = new BigInteger(281, new Random()).toString(13);
currentSaveName = "snapshotTemp-" + unique;
saveSnapshot(null);
}
}
}
Obviously it is a good idea to check if a snapshot with the generated name already exists. How should I actually do it?
The list of existing saved games can be retrieved by calling [Snapshots.load()](https://developers.google.com/android/reference/com/google/android/gms/games/snapshot/Snapshots#load(com.google.android.gms.common.api.GoogleApiClient, boolean)). This is an asynchrounous call, so one way to use it is to call it before saving and keep the names in a list which you can then compare to the new name.
The sample CollectAllTheStars (https://github.com/playgameservices/android-basic-samples) demonstrates how to use this API to display a custom view to select a saved game.
Games.Snapshots.load(mGoogleApiClient, false).setResultCallback(
new ResultCallback<Snapshots.LoadSnapshotsResult>() {
#Override
public void onResult(Snapshots.LoadSnapshotsResult loadSnapshotsResult) {
mSavedGamesNames = new ArrayList<String>();
for (SnapshotMetadata m :loadSnapshotsResult.getSnapshots()) {
mSavedGamesNames.add(m.getUniqueName());
}
}
});

Android remove and add to ArrayList in between activities

I'm making my first android app and here's where i'm stuck.
I have an activity A which requires 4 players to be picked.
I'm passing to the activity PickPlayer 1,2,3,4 according to which player i want to fill.
ImageButton addp1 = (ImageButton)findViewById(R.id.player1);
addp1.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v)
{
Intent i = new Intent(getApplicationContext(), PickPlayer.class);
i.putExtra("playersList", playersList);
startActivityForResult(i, 1);
}
});
On the PickPlayer activity i have a list which is populated and each item receives a listener.
final ArrayList<Player> playersList = (ArrayList<Player>)getIntent().getSerializableExtra("playersList");
lv.setAdapter(new PlayerItemAdapter(this, android.R.layout.simple_list_item_1, playersList));
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapter, View view, int position, long arg) {
player = playersList.get(position);
playersList.remove(position);
Intent intentMessage = new Intent();
intentMessage.putExtra("player", player);
intentMessage.putExtra("playersList", playersList);
setResult(RESULT_OK, intentMessage);
finish();
}
});`
The above works fine by creating the playersList on activity A and passing it through from one to another each time and removing the player from the playerList on click.
Problem is if a player is chosen by mistake he needs to be put back into the list again once replaced by someone else.
Any suggestions on implementing this ?
One way i thought of is to pass from activity A to PickPlayer the player ( if one is assigned already at his position ) and readding him to the playerList again but i'm sure there's a better way for it.
I'm new to android so i have no idea about resources and best practises.
( example passing an object through activities or an id and run a db query).
Thanks
IMO, you'll achieve best results with a singleton class (or methods on Application instance - here a good stackoverflow question about it).
Your array would be an internal member of the singleton and have an boolean attribute to indicate if a player is already picked or not. Some methods using this attribute can be implemented like:
List<Player> getPickedPlayers()
List<Player> getNotPickedPlayers()
void setPlayerPicked(Player player)
void setPlayerNotPicked(Player player)
and so on...
Hope it helps!
When you send object through an Intent's bundle (i.putExtra("playersList", playersList);), it is marshalled and then unmarshalled on the other side (the new activity). This mean you have 2 instances of ArrayList and its content (one in each activity). If you wish to share data between activity A and activity B, I suggest you store it on an Application instance or by using a singleton.
If your data is coming from a database, you can pass the id through the intent, and get the list of players and the special player with a database query.
Not sure if this is the best way to accomplish this but i'm going to share with you.
I move the arrayList with the players back and forth in between the activities.
Once the player is sent back it's removed from it and kept in an object player1,player2,player3 etc etc.
So if the user clicks the button that has already a player assigned to it i simply add that player again into the list and pass the arrayList as i would do if it was empty.

Sharing data between activitiesin Android

I am programming an android app that does the following (in a nutshell):
Creates an activity that listens to data streaming in via bluetooth
Parses the data streamed in
(Plan) Display the interpreted meaning of the data, like a graph/text
For #3, I am planning to create a new activity that sits on top of the Bluetooth Streaming activity -- after all, the bluetooth matters should be in the background after initial set up.
However, I am having issues sending the data between the BlueTooth activity and the display activity.
I have googled, researched and the best I can come up with is using Intents for this, but its not working.
Here is an example. Say data is coming in and I interpret the data involve body movement:
private BodyMovementPositionStream() {
if (getActivity() != null && getContext() != null && iDebug == null) {
iDebug = new Intent(getActivity(), AGDebug.class);
iDebug.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
new Thread(new Runnable() {
public void run() {
if (theApplication.isD()) {
Log.d(theApplication.getTAG(), "Creating Intent for Position Debuging...");
getContext().registerReceiver(new AGDReceiver(), new IntentFilter("com.ekscsy.sleepstudy.debug.AGDebug"));
getContext().startActivity(iDebug);
}
}
}).start();
}
}
So basically, in that snip-it, I start the activity AGDebug, add the flags for new task, and then I register the receiver in a new thread. This works, I see it on my phone and the Log.d() message shows up.
Later on in my code, I extract the data:
private static final int fSize = 4;
public void unpack(byte[] data) {
int i = -fSize;
accx = Streams.byteArr2ByteBuff(data, i += fSize, fSize).order(null).getFloat();
accy = Streams.byteArr2ByteBuff(data, i += fSize, fSize).order(null).getFloat();
accz = Streams.byteArr2ByteBuff(data, i += fSize, fSize).order(null).getFloat();
gyrox = Streams.byteArr2ByteBuff(data, i += fSize, fSize).order(null).getFloat();
gyroy = Streams.byteArr2ByteBuff(data, i += fSize, fSize).order(null).getFloat();
gyroz = Streams.byteArr2ByteBuff(data, i += fSize, fSize).order(null).getFloat();
temp = Streams.byteArr2ByteBuff(data, i += fSize, fSize).order(null).getFloat();
Intent intent = new Intent("com.ekscsy.sleepstudy.debug.AGDReceiver");
intent.putExtra("com.ekscsy.sleepstudy.debug.AGDebug.accxF", accx);
getContext().sendBroadcast(intent);
}
At the end of the extraction, I make a new intent, .putExtra() the name of the package + a float with some data. I do the .sendBroadcast() but... nothing happens. I have a breakpoint that never gets called:
public class AGDReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
assert(true); //breakpoint here
}
}
Obviously, I am missing steps here, but I can't seem to find any good information on sharing data between activities.
In fact, let me re-iterate, the main goal here is the share data between activities. Data, being floats, ints, etc. I am assuming the "Android" way of doing this is through intents and broadcast, but if there is a better method, I am all ears.
I am looking to make this app compatible with API 8.
EDIT: FYI, I changed the code to take the start of the AGDebug activity out of its own thread. It looks more like this now:
if (getActivity() != null && getContext() != null && iDebug == null) {
iDebug = new Intent(getActivity(), AGDebug.class);
iDebug.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Log.d(theApplication.getTAG(), "Creating Intent for Position Debuging...");
getContext().registerReceiver(new AGDReceiver(), new IntentFilter("com.ekscsy.sleepstudy.debug.AGDebug"));
getContext().startActivity(iDebug);
}
Still didn't change anything though. I thought maybe there was an issue of running the activity between different manually created threads.
Major Update/New Problem:
I found this guide http://www.sohailaziz.com/2012/04/localbroadcastmanager-intra-application.html and was able to successfully implement its strategy of making the broadcaster work with the LocalBroadcastManager. However... and this is very weird... I am finding the onReceieve() method gets called sometimes or not. IOW: when I run the app, either it will get called each time I say lbm.sendBroadcast(intent) or it won't get called at all.
Very puzzling.
Final Edit:
Okay I got it to work. There is a delicate art of making sure each intent is named correctly and points to the right spot. Once done, it seems to be consistent.
You register the broadcast receiver like this:
getContext().registerReceiver(new AGDReceiver(),
new IntentFilter("com.ekscsy.sleepstudy.debug.AGDebug"));
but you broadcast this Intent:
Intent intent = new Intent("com.ekscsy.sleepstudy.debug.AGDReceiver");
The actions in the Intents are not the same. You could have saved yourself this trouble if you defined a constant for this, ie:
public static final String MY_INTENT_ACTION = "com.ekscsy.sleepstudy.debug.AGDReceiver";
and then used that constant when you create the broadcast intent and when you create the intent filter for registering the broadcast receiver.

Get result from an activity after finish(); in an Android unit test

I'm currently writing some Android unit tests, and while I've gotten most things to work the way I want, one thing has left me kind of stumped.
I have the following code in my activity under test:
Intent result = new Intent();
result.putExtra("test", testinput.getText().toString());
setResult(Activity.RESULT_OK, result);
finish();
I'm trying to figure out how to use Instrumentation (or whatever) to be able to read the result of the activity, or get at the intent after the activity is finished.
Can anyone help?
You can use reflection and grab the values directly from the Activity.
protected Intent assertFinishCalledWithResult(int resultCode) {
assertThat(isFinishCalled(), is(true));
try {
Field f = Activity.class.getDeclaredField("mResultCode");
f.setAccessible(true);
int actualResultCode = (Integer)f.get(getActivity());
assertThat(actualResultCode, is(resultCode));
f = Activity.class.getDeclaredField("mResultData");
f.setAccessible(true);
return (Intent)f.get(getActivity());
} catch (NoSuchFieldException e) {
throw new RuntimeException("Looks like the Android Activity class has changed it's private fields for mResultCode or mResultData. Time to update the reflection code.", e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Or you could also use Robolectric and shadow the Activity under test. Then, ShadowActivity provides you with methods to easily know if an Activity is finishing and for retrieving its result code.
As an example, one of my tests looks like this:
#Test
public void testPressingFinishButtonFinishesActivity() {
mActivity.onCreate(null);
ShadowActivity shadowActivity = Robolectric.shadowOf(mActivity);
Button finishButton = (Button) mActivity.findViewById(R.id.finish_button);
finishButton.performClick();
assertEquals(DummyActivity.RESULT_CUSTOM, shadowActivity.getResultCode());
assertTrue(shadowActivity.isFinishing());
}
You can do this by writing a special activity whose only purpose is to start the activity you are testing for result and save the result for you to assert correctness on.
For example, you could create an activity named ResultReceiverActivity. Give it three methods: getResultCode, getResultData, and getReceivedRequestCode, which can be used to verify that the tested activity returned the right values. You would create a test case that extends ActivityInstrumentationTestCase2 and the generic parameter would be ResultReceiverActivity. Calling getActivity will get you the activity instance.
public class ReturnedResultTest
extends ActivityInstrumentationTestCase2<ResultReceiverActivity> {
public void testReturnedResult() {
ResultReceiverActivity a = getActivity();
assertEquals(Activity.RESULT_OK, a.getResultCode());
assertEquals("myResult", a.getResultData().getStringExtra("test"));
assertEquals(0x9999, a.getReceivedRequestCode());
}
}
ResultReceiverActivity needs to override onActivityResult, of course, and should just store the values of that methods parameter in its fields, like so:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
this.receivedRequestCode = requestCode;
this.resultCode = resultCode;
this.resultData = data;
}
Of course, you may want to customize the activity that ResultReceiverActivity starts, and you can easily do that by using getIntent in its onCreate method. In your test case, call setActivityIntent before calling getActivity to set which Intent is used to start the activity.
I'm not sure if it is different for unit tests, but you should be able to use onActivityResult as seen here: StartingActivities. You just start the Activity with startActivityForResult(intent, requestCode) and then use
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
back in the activity that used startActivityForResult.

Categories