I have an app where i can submit a timestatus to a server.
To save battery I queue the statusitems in a SQLite Database and submit them periodically with a JobScheduler who starts an IntentService to the server.
The function to insert the items looks like this:
public synchronized void workThroughQueue() {
try {
for (QueueItem queueItem : getAllQueueItems()) {
try {
dao.insert(queueItem.getItem());
delete(queueItem);
} catch (Exception e) {
Log.e(TAG, "Queueitem konnte nicht verarbeitet werden: " + e.getMessage(), e);
}
}
} catch (Exception e) {
Log.e(TAG, "Queueverarbeitung nicht vollständig: " + e.getMessage(), e);
}
}
The service (kotlin):
class QueueService : IntentService(QueueService::class.java.name) {
override fun onHandleIntent(intent: Intent?) {
Log.d("QueueService", "Jobservice started")
TimerecordQueue().workThroughQueue()
DangerAllowanceQueue().workThroughQueue()
ProjektEndQueue().workThroughQueue()
PhotoUploadQueue().workThroughQueue()
}
}
My problem is, if the process gets killed by the system cause of low memory during dao.insert(queueItem.getItem()); it sometimes gets successfully submitted to the server but it doesn't get deleted of the queue.
So the next time the queue starts it gets submitted again.
How can i resolve this problem?
Well, in this case you can do one of the two things
In the service after insertion keep the insertion record locally(in your local database) as boolean flag as well. That will let you know for sure that your records were submitted successfully.
Second way is, when you are hitting a service, on server database before inserting record check that record is already exist. As I can see you are sending item. There should be some sort of item id. Check on server database if that record against item id exists if yes leave that record.
This should do the trick.
Related
In my current project I have a section where a user essentially enters a key to get the data under that key in firebase. I am using the get method as it only needs to be accessed once. The get method has an on Success and on failure listener and I have the entire thing within a try catch block but for some reason the app still bugs out and does a weird like crash and refresh action when a key that's not in the realtime database is entered. I ran a debug and for some reason when it reaches the get method in this situation instead of going to the catch block it leaves the function completely.
fun joinFinalised(newEmail: String, code: String) {
if (code.isEmpty()){
Toast.makeText(this, "No code entered", Toast.LENGTH_SHORT).show()
} else {
try {
fDBRef.child(newEmail.replace(".","~dot~")).get().addOnSuccessListener {
if (code == it.child("Code").value as String){
email = it.child("Email").value as String
fDBRef.child(email.replace(".","~dot~")).setValue(newCrew)
displayData()
}
}.addOnFailureListener{
Toast.makeText(this, "Join failed", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
Toast.makeText(this, "Join failed", Toast.LENGTH_SHORT).show()
}
}
}
The error it gives is java.lang.NullPointerException: null cannot be cast to non-null type kotlin.String which points to the line of the if statement where I compare the code.
I fixed the problem by adding a second try catch around the if statement where the error pointed to
I need to retrieve some data from roughly 50 different URLS with the press of a button.
The code goes through them one at a time, and although it doesn't take that long, it will take around 20 seconds, and I have all this code running inside of a button.
I was hoping I could update a TextView or something to say "Loading page 1 of 50" then "Loading page 2 of 50" etc, in between accessing the different websites.
The code below works, just the button gets stuck down for an unknown amount of time, and I want the user to have some indication of how far along the loading is doing.
btnGetData.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
//regionRetrieve 1 page of auction data, so we know how many future pages to retrieve. -P
String auctionURL = "https://api.hypixel.net/skyblock/auctions?page=";
String firstPage = null;
try {
firstPage = new RetrieveData().execute(auctionURL + "0").get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
auctionInfo = new JSONObject(firstPage);
} catch (JSONException e) {
e.printStackTrace();
}
//endregion
//regionRetrieve the remaining pages
int totalPages = 0;
try {
totalPages = auctionInfo.getInt("totalPages");
} catch (JSONException e) {
e.printStackTrace();
}
//Place to put the rest of the pages
ArrayList<String> remainingPages = new ArrayList<>();
//Starts at 1, because we already retrieved the 0 page as the first page.
//Also, I checked, and you do need to retrieve the 52nd page if there are say, 52 pages.
for (int i = 1; i <= totalPages; ++i) {
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//ADD SOME KIND OF NOTIFICATION HERE
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
String newPage = null;
try {
newPage = new RetrieveData().execute(auctionURL + i).get();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
remainingPages.add(newPage);
}
Toast.makeText(getApplicationContext(),"All data received.",Toast.LENGTH_SHORT).show();
tvLoading.setVisibility(View.GONE);
//endregion
//stuff below this point is irrelevant to the question
}
});
I tried wrapping all of the code above inside an AsyncTask, and utilizing the "onProgressUpdate", but that did not work. Furthermore, I heard that by now, AsyncTask has been deprecated, and that there are better ways to do it.
I also tried using Toast messages, but they all show up at the end, which kind of defeats the purpose.
I even put the Toast messages in the AsyncTasks that I call in order to get the data, but that didn't work either. (The RetrieveData() is an AsyncTask that reads all the information from the URLS, and returns it as a String. I know you aren't supposed to use get, but in this case it is important the data arrives in the correct order. Unless, after retrieving the first one, and knowing how many pages there are, I could launch 50 threads at the same time to retrieve the data? But still, you are limited by your internet connection, and the user is still sitting there confused.)
Is there a proper way to do this?
Any help would be appreciated!
Instead of putting all the code in the button, make the button launch another activity, and put the data retrieval code in that activity.
Launch the code inside of a thread, and from within the thread, update UI elements to inform the user of the progress.
After the data is retrieved, store it, then start an Intent to go back to the previous activity, access the data where you stored it, and use it for whatever you needed to do.
(I know I answered my own question, but I figured it out a few hours later and nobody had responded up until now, hopefully this helps someone in the future.)
i am building my app on android repository by Fernando Cejas and i have a problem with subscribing to observable after calling dispose.
When i come to dashboard, i call method subscribeOnUserMessages.execute(new Subscriber(), new Params(token)), which is method in UseCase class
public void execute(DisposableObserver<T> observer, Params params) {
Preconditions.checkNotNull(observer);
final Observable<T> observable = this.buildUseCaseObservable(params)
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler());
addDisposable(observable.subscribeWith(observer));
}
In child class SubscribeOnUserMessages i simply call repository like this
return messageRepository.subscribeOnUserMessages(params);
In my socket implementation i create like this
return Observable.create(emitter -> {
if (!isThereInternetConnection()) {
Timber.w("Network connection exception");
emitter.onError(new NetworkConnectionException());
return;
}
/*
* Open socket if not opened
*/
openSocket(params.getToken());
String channelName = CHANNEL_PRIVATE_USER + params.getAuthenticated().getUuid();
if (subscribedChannels.contains(channelName)) {
Timber.d("Channel %s is already subscribed", channelName);
return;
}
JSONObject auth;
try {
auth = createAuthJson(CHANNEL, channelName, params.getToken());
} catch (JSONException e) {
Timber.e("Couldn't create auth json");
emitter.onError(e);
return;
}
mSocket.emit(SUBSCRIBE, auth);
Timber.d("Emitted subscribe with channel: %s ", CHANNEL_PRIVATE_USER + params.getAuthenticated().getUuid());
subscribedChannels.add(CHANNEL_PRIVATE_USER + params.getAuthenticated().getUuid());
Timber.d("Subscribing on event: %s\n with user: %s", EVENT_USER_NEW_MESSAGE, params.getAuthenticated().getUuid());
if (mSocket.hasListeners(EVENT_USER_NEW_MESSAGE)) {
Timber.v("Socket already has listener on event: %s", EVENT_USER_NEW_MESSAGE);
return;
}
mSocket.on(EVENT_USER_NEW_MESSAGE, args -> {
if (args[1] == null) {
emitter.onError(new EmptyResponseException());
}
Timber.d("Event - %s %s", EVENT_USER_NEW_MESSAGE, args[1].toString());
try {
MessageEntity messageEntity = messageEntityJsonMapper.transform(args[1]);
emitter.onNext(messageEntity);
} catch (JSONException e) {
Timber.e(e, "Could not parse message json");
emitter.onError(e);
}
});
});
Symptoms are that first time i subscribe everything is going through to presentation layer. When i dispose after going to second screen and come back i only see logs coming to socket implementation, but not going through.
My question is: Is there a method for subscribing to same observable again? I've already tried to save that observable in my use case in singleton and subscribe to that observable, didn't help.
Without additional info and details regrading socket implementation it is hard to spot the problem exactly, but, from the code you've posted, you don't have dispose logic, so while you might properly call dispose() to the Observable at the correct lifecycle event, your socket will actually stay open, and it might not got disconnected/closed properly ever.
That might lead to a problems opening and connecting to the socket at the 2nd time, as you might try to reopen already open socket and depends on your internal socket impl that might be a problem.
(I can see in the comment that openSocket if not already opened, but still there might be problem elsewhere calling some method on the socket multiple times or setting listeners, again depends on the socket impl)
As a general guidelines, you should add dispose logic using emitter.setCancellable()/emitter.setDisposable() in order to dispose properly the socket resources when you no longer need them, thus - when applying subscribe again (whether the same object or not) will invoke your subscription logic again that will reopen the socket and listen to it.
It is not clear to me if you like to keep the socket open when you moving to a different screen (I don't think it is a good practice, as you will keep this resource open and might never get back to the screen again to use it), but if that's the case as #Phoenix Wang mentioned, you can use publish kind operators to multicast the Observable, so every new Subscriber will not try to reopen the socket (i.e. invoking the subscription logic) but will just get notify about messages running in the already opened socket.
I have been able to successfully cast video to a Chromecast and have the option let the video play when disconnecting and it all works great. However, if I choose to quit the application and let the video continue playing and then try to re-join the currently playing session and try to use the RemoteMediaPlayer to control the video I am getting: "java.lang.IllegalStateException: No current media session".
Just as a background, I am saving the route id and session id on the initial connect into preferences and am able to successfully call "Cast.CastApi.joinApplication" and when in the onResult I am recreating the Media Channel and setting the setMessageReceivedCallbacks like so:
Cast.CastApi.joinApplication(mApiClient,"xxxxxxxx",persistedSessionId).setResultCallback(new ResultCallback<Cast.ApplicationConnectionResult>() {
#Override
public void onResult(Cast.ApplicationConnectionResult applicationConnectionResult) {
Status status = applicationConnectionResult.getStatus();
if (status.isSuccess()) {
mRemoteMediaPlayer = new RemoteMediaPlayer();
mRemoteMediaPlayer.setOnStatusUpdatedListener(
new RemoteMediaPlayer.OnStatusUpdatedListener() {
#Override
public void onStatusUpdated() {
Log.d("----Chromecast----", "in onStatusUpdated");
}
});
mRemoteMediaPlayer.setOnMetadataUpdatedListener(
new RemoteMediaPlayer.OnMetadataUpdatedListener() {
#Override
public void onMetadataUpdated() {
Log.d("----Chromecast----", "in onMetadataUpdated");
}
});
try {
Cast.CastApi.setMessageReceivedCallbacks(mApiClient,mRemoteMediaPlayer.getNamespace(), mRemoteMediaPlayer);
} catch (IOException e) {
Log.e("----Chromecast----", "Exception while creating media channel", e);
}
//-----------RESOLUTION START EDIT------------------
mRemoteMediaPlayer.requestStatus(mApiClient).setResultCallback(new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
#Override
public void onResult(RemoteMediaPlayer.MediaChannelResult mediaChannelResult) {
Status stat = mediaChannelResult.getStatus();
if(stat.isSuccess()){
Log.d("----Chromecast----", "mMediaPlayer getMediaStatus success");
// Enable controls
}else{
Log.d("----Chromecast----", "mMediaPlayer getMediaStatus failure");
// Disable controls and handle failure
}
}
});
//-----------RESOLUTION END EDIT------------------
}else{
Log.d("----Chromecast----", "in status failed");
}
}
}
If I declare the RemoteMediaPlayer as static:
private static RemoteMediaPlayer mRemoteMediaPlayer;
I can join the existing session as well as control the media using commands like:
mRemoteMediaPlayer.play(mApiClient);
or
mRemoteMediaPlayer.pause(mApiClient);
But once I quit the application obviously the static object is destroyed and the app produces the aforementioned "No current media session" exception. I am definitely missing something because after I join the session and register the callback perhaps I need to start the session just like it was creating when I initially loaded the media using mRemoteMediaPlayer.load(.
Can someone please help as this is very frustrating?
The media session ID is part of the internal state of the RemoteMediaPlayer object. Whenever the receiver state changes, it sends updated state information to the sender, which then causes the internal state of the RemoteMediaPlayer object to get updated.
If you disconnect from the application, then this state inside the RemoteMediaPlayer will be cleared.
When you re-establish the connection to the (still running) receiver application, you need to call RemoteMediaPlayer.requestStatus() and wait for the OnStatusUpdatedListener.onStatusUpdated() callback. This will fetch the current media status (including the current session ID) from the receiver and update the internal state of the RemoteMediaPlayer object accordingly. Once this is done, if RemoteMediaPlayer.getMediaStatus() returns non-null, then it means that there is an active media session that you can control.
As user3408864 pointed out, requestStatus() after rejoining the session works. Here is how i managed to solve it in my case and it should work in yours.
if(MAIN_ACTIVITY.isConnected()){
if(MAIN_ACTIVITY.mRemoteMediaPlayer == null){
MAIN_ACTIVITY.setRemoteMediaPlayer();
}
MAIN_ACTIVITY.mRemoteMediaPlayer.requestStatus(MAIN_ACTIVITY.mApiClient).setResultCallback( new ResultCallback<RemoteMediaPlayer.MediaChannelResult>() {
#Override
public void onResult(RemoteMediaPlayer.MediaChannelResult mediaChannelResult) {
if(playToggle ==0){
try {
MAIN_ACTIVITY.mRemoteMediaPlayer.pause(MAIN_ACTIVITY.mApiClient);
playToggle =1;
} catch (IOException e) {
e.printStackTrace();
}
}else{
try {
MAIN_ACTIVITY.mRemoteMediaPlayer.play(MAIN_ACTIVITY.mApiClient);
playToggle =0;
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
}
Ignore, MAIN_ACTIVITY, it is just a static reference to my activity since i run this piece of code from a Service. Also, setRemoteMediaPlayer() is a method where i create a new RemoteMediaPlayer() and attach the corresponding Listeners.
Hopefully this helps. Also, sorry if any mistake, it is my first post to StackOverFlow.
I have to Users (User A and B) and one Chromecast device (C1).
User B starts a stream on C1.
User A connects to C1
Now User A should be able to control the stream running on C1. But every time I want to start a session the running stream on C1 is shut down and the receiver app is restarting.
Is there a way to join an active session? Or is that a job which has to be done by the web app running on the Chromecast device?
EDIT:
my sender app is a native Android app
Thanks!
You should have a look to the TicTacToe application. I think it does exactly that where 2 players can join the same game :
https://github.com/googlecast/cast-android-tictactoe
Hope this helps.
JN
What sort of sender are you using? Is it a native app (i.e. using Android or iOs SDK on a mobile device) or the sender is a chrome app?
On the receiver, you create a Receiver object and a ChannelHandler. You use the receiver to generate a ChannelFactory which you then pass to the ChannelHandler. The ChannelHandler now handles the creation of channels on the receiver. You will want to add an EventListener to the handler to listen to messages. Based on those messages you can do various things.
receiver = new cast.receiver.Receiver(YOUR_APP_ID, [YOUR_PROTOCOL], "", 5);
var dashHandler = new cast.receiver.ChannelHandler(YOUR_PROTOCOL);
dashHandler.addChannelFactory(receiver.createChannelFactory(YOUR_PROTOCOL));
dashHandler.addEventListener(cast.receiver.Channel.EventType.MESSAGE, onMessage.bind(this));
receiver.start();
...
onMessage = function (e) {
var message = e.message;
switch (message.type) {
...
}
}
On the sender, after a session is created you will want to send a check status message to the receiver to see if there are already channels attached. You can do this via your MessageStream and your receiver needs to respond in such a way that the MessageStream gets its status updated. You check that status to see if there are channels. If there are you can start listening to updates for your receiver. If not you can send a load event to the receiver to start your activity.
MediaProtocolCommand cmd = mMessageStream.requestStatus();
cmd.setListener(new MediaProtocolCommand.Listener() {
#Override
public void onCompleted(MediaProtocolCommand mPCommand) {
if (mMessageStream.getState() == 'channelsExist') {
//Start New Activity
} else {
//Join Existing Activity
}
#Override
public void onCancelled(MediaProtocolCommand mPCommand) {
}
});
This is kind of a vague response, but it could be more specific if I knew what you were trying to do. My app is using Google's RAMP protocol to play videos so my MessageStream and all it's messages are already defined. If you're doing something different, you need to create your own MessageStream.
Sorry for the late answer, but I figured it out by myself: It wasn't such complicated at all
I started the an Application like this
try {
mSession.startSession(applicationName,applicationArgs);
} catch (IllegalStateException e) {
Log.e(getClass().getSimpleName(), e.getMessage(), e);
} catch (IOException e) {
Log.e(getClass().getSimpleName(), e.getMessage(), e);
}
But it seems, that the MimeData applicationArgs is not needed at all. By removing the arguments and starting the session like below it works really fine!
try {
mSession.startSession(applicationName);
} catch (IllegalStateException e) {
Log.e(getClass().getSimpleName(), e.getMessage(), e);
} catch (IOException e) {
Log.e(getClass().getSimpleName(), e.getMessage(), e);
}
I hope this works for you too!