I cannot decide how to implement this task correctly using RxJava2.
The problem is following. I am recording audio using AuidoRecord.
Currently I have implemented the custom Flowable class like that
private class StreamAudioRecordRunnable extends Flowable<short[]> implements Runnable {
private int mShortBufferSize;
private List<Subscriber<? super short[]>> mSubscribers = new ArrayList<>();
private short[] mAudioShortBuffer;
private void removeAllNullableSubscribers() {
mSubscribers.removeAll(Collections.singleton(null));
}
private void notifyAllSubscribers(short[] audioBuffer) {
removeAllNullableSubscribers();
for (Subscriber<? super short[]> subscriber : mSubscribers) {
subscriber.onNext(audioBuffer);
}
}
#Override
protected void subscribeActual(Subscriber<? super short[]> newSubscriber) {
mSubscribers.add(newSubscriber);
}
private void notifyAllSubscribersAboutError(Throwable error) {
for (Subscriber<? super short[]> subscriber : mSubscribers) {
subscriber.onError(error);
}
}
#Override
public void run() {
// Init stuff
while (mIsRecording.get()) {
int ret;
ret = mAudioRecord.read(mAudioShortBuffer, 0, mShortBufferSize);
notifyAllSubscribers(mAudioShortBuffer);
}
mAudioRecord.release();
}
}
As you can see I am manually adding subscribers to the list. Then when I get new buffer all subscribers are notified.
I am guessing that this is not the most performant way to do this.
What I need
As far as this flowable running in a service. It should run until the service is alive, even if there are no subscribers.
Subscribers are not constant, they may subscribe and then unsubscribe, but the Flowable/Observable should still be running.
As the data emitted by the Flowable is the stream, subscribers should not be notified about already emitted items, they should only get current streaming data. Fire and forget.
The Flowable should run even all subscribers are gone.
Please suggest the right strategy to implement this.
I would be grateful for any help.
Something like
public class StreamAudioRecordRunnable {
private int mShortBufferSize;
private short[] mAudioShortBuffer;
private ConnectedFlowable<short[]> audioFlowable();
public StreamAudioRecordRunnable() {
audioFlowable = Flowable.create(new ObservableOnSubscribe<short[]>() {
#Override
public void subscribe(FlowableEmitter<short[]> emitter) throws Exception {
try {
while (mIsRecording.get()) {
int ret;
ret = mAudioRecord.read(mAudioShortBuffer, 0, mShortBufferSize);
emitter.onNext(mAudioShortBuffer);
}
emitter.onComplete();
mAudioRecord.release();
} catch (Exception e) {
emitter.onError(e);
mAudioRecord.release();
}
}
}).subscribeOn(Schedulers.io()).publish();
}
public Flowable<short[]> getFlowable() {
return audioFlowable.hide();
}
#Override
public void start() {
audioObservable.connect();
}
}
would be my preference.
Related
I have a function which is supposed to return a list from the result of a Task API.
#Override
public List performQuery(boolean isPaginationQuery, boolean isSortingQuery {
try {
TaskImpl taskImpl = new TaskImpl(isPaginationQuery,
isSortingQuery);
queryExecutor.submit(taskImpl).get();
return taskImpl.get();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Inner class which performs the updates
private class TaskImpl extends Task<List> {
private boolean isPaginationQuery, isSortingQuery;
public TaskImpl(boolean isPaginationQuery, boolean isSortingQuery) {
this.isPaginationQuery = isPaginationQuery;
this.isSortingQuery = isSortingQuery;
}
#Override
protected List call() throws Exception {
Platform.runLater(() -> {
loaderContainer.setVisible(true);
loaderContainer.toFront();
});
HSession hSession = new HSession();
TaskInfoDao taskInfoDao = new TaskInfoDaoImpl(hSession.getSession(), currentConnection.getConnectionId());
if (!isPaginationQuery && !isSortingQuery) {
paginator.setTotal(taskInfoDao.getTaskInfoWithFiltersCount(paginator.getFilterMap(), false));
}
Stream<TaskInfo> resultStream = taskInfoDao.getTaskInfoWithFilters(paginator.getFilterMap(), false,
paginator.getStartIndex() * paginator.getPageSize(),
paginator.getPageSize() * paginator.getPageGap());
List<TaskInfoTableView> data = createData(resultStream);
hSession.close();
return data;
}
#Override
protected void succeeded() {
super.succeeded();
try {
//set the pagination if the task is complete
//and it is not a pagination query
if (!isPaginationQuery) {
((TaskInfoViewController) uiController).setPagination(
FXCollections.observableArrayList(get()));
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
#Override
protected void cancelled() {
super.cancelled();
updateMessage("Cancelled!");
}
#Override
protected void failed() {
super.failed();
updateMessage("Failed!");
}
}
performQuery function calls the thread and waits for its result.
The loader is being displayed from inside the TaskImpl class using Platform.runLater.
But the loader does not appear until the task has finished i.e. loader appears after the completion of call() function's execution.
When i remove the taskImpl.get() the loader works fine.
Any help is appreciated.
P.S. : Under any case, I need the result of the Task API outside the Inner class( outside TaskImpl )
First of all, it seems like you are not very familiar with asynchronous programming. Having performQuery() to return a List shows that you are expecting to run this synchronously - there is no way for you to return results before you get the results. This is exactly why you are freezing your UI.
The important thing to understand about asynchronous programming is, you would start doing something (i.e. a task) in another thread, and return immediately. When there is result returned from the task, you switch back to the UI (JavaFX Application) thread to update it. You can see this as event-driven approach.
Therefore, for your case, you should directly update the list (the list which you are returning in performQuery()) in the succeeded() method that you have overridden in TaskImpl class.
If the list that you should be updating is not in the scope of TaskImpl, then you can the functional interfaces in java.util.function package to do it for you. This means that you would create that functional interface object at the right scope, and pass in into TaskImpl during object construction, and call that interface in succeeded().
Update
If I assume this is what calls performQuery():
public class MyController {
#FXML
TableView<Foo> tableView;
public void initialize() {
List result = queryController.performQuery(true, true);
tableView.getItems().addAll(result);
}
}
Then, I would probably do something like this:
public class MyController {
#FXML
TableView<Foo> tableView;
public void initialize() {
List result = queryController.performQuery(true, true, list -> tableView.getItems.addAll(list));
}
}
public class QueryController {
#Override
public void performQuery(boolean isPaginationQuery, boolean isSortingQuery, java.util.function.Consumer<List> onQuerySucceeded) {
try {
TaskImpl taskImpl = new TaskImpl(isPaginationQuery,
isSortingQuery, onQuerySucceeded);
queryExecutor.submit(taskImpl);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
private class TaskImpl extends Task<List> {
private final java.util.function.Consumer<List> onQuerySucceeded;
public TaskImpl(boolean isPaginationQuery, boolean isSortingQuery, java.util.function.Consumer<List> onQuerySucceeded) {
this.isPaginationQuery = isPaginationQuery;
this.isSortingQuery = isSortingQuery;
this.onQuerySucceeded = onQuerySucceeded;
}
#Override
protected void succeeded() {
super.succeeded();
// Not sure what the original codes are doing.
try {
//set the pagination if the task is complete
//and it is not a pagination query
if (!isPaginationQuery) {
((TaskInfoViewController) uiController).setPagination(
FXCollections.observableArrayList(get()));
}
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// This is what is being added in
onQuerySucceeded.accept(this.getValue());
}
}
Declaration
#GET("api/Game/SearchGames")
Observable<List<GameModel>> searchGames();
This is the network call
public static Observable<List<GameModel>> searchGames () {
VersusAPI client = VersusServiceGenerator.createService(VersusAPI.class);
Observable<List<GameModel>> ob = client.searchGames();
return ob;
}
Here is where I implement.
mAdapterMyGames = new RecyclerViewAdapter(searchGames());
searchGames() returns rx.Observable<java.util.list<GameModel>>. How do I change that to only java.util.list<GameModel>?
You don't properly understand what is an Observable.
It is an object, to which You can subscribe() to get the result of it's operation. Usually, only when subscribing to an Observable it starts and you can get the result inside Subscriber's onNext() function.
So in your case:
Subscribe to this Observable.
Look for the result inside this subscriber's onNext function.
searchGames().subscribe(new new Subscriber<List<GameModel>>() {
#Override
public void onNext(List<GameModel> gameModels) {
//TODO make sth useful with models
}
#Override
public void onCompleted() { }
#Override
public void onError(Throwable e) { }
)
I've created a new log handler for JUL which extends java.util.logging.Handler.
Is there any standardized way how I can react to errors that occur during the processing of the LogRecord (like LogLog in log4j)?
Just using a JUL-Logger results in another LogRecord that has to be processed by the very same handler.
So, I'm basically looking for a (standard) way how to log messages without creating an endless loop. At the moment, I'm comparing the sourceClassName to prevent such a loop.
Exceptions that occur inside of the handler should be reported using Handler.reportError(String, Exception, int). This reports failures to the installed ErrorManager which can be customized. Using that should take care of most of the endless loops.
However, if act of publishing relies on a lib that also generates log records then you have to resort to detecting the loop. Use a java.lang.ThreadLocal and some sort of enum to track the state changes.
public class HandlerReentrance extends Handler {
private static final Level PUBLISH = Level.ALL;
private static final Level REPORT = Level.OFF;
private static final ThreadLocal<Level> LOCAL = new ThreadLocal<>();
#Override
public void publish(LogRecord record) {
if (LOCAL.get() == null) {
LOCAL.set(PUBLISH);
try {
doPublish(record);
} finally {
LOCAL.remove();
}
} else {
final Level last = LOCAL.get();
if (PUBLISH.equals(last)) {
LOCAL.set(REPORT);
try {
reportLoop(record);
} finally {
LOCAL.set(last);
}
}
}
}
private void doPublish(LogRecord record) {
if (isLoggable(record)) {
//Insert code.
}
}
private void reportLoop(LogRecord record) {
//Insert code.
}
#Override
public void flush() {
}
#Override
public void close() {
}
}
At the moment, I'm going for this solution:
public class MyHandler extends Handler {
private final static String className = MyHandler.class.getName();
private final static Logger logLogger = Logger.getLogger(className);
...
#Override
public void publish(LogRecord record) {
if (!super.isLoggable(record)) {
return;
}
String loggerName = record.getLoggerName(); //EDIT: Was getSourceClassName before
if (loggerName != null && loggerName.equals(className)) {
// This is our own log line; returning immediately to prevent endless loops
return;
}
try {
// ... do actual handling...
} catch(Exception e) {
logLogger.logp(level.SEVERE, className, "publish", "something went wrong", e);
}
}
}
I'm trying to write a callback system in Java that works similar to that of Javascripts, what I'm doing is I'm sending information across the network that has a "callback id" attached to it. When the client receives this data back from the server, it should locate the callback for that id form a collection and call it with the retrieved data.
Here's the current system I've written up while trying to achieve this:
public class NetworkCallback {
private int id;
private Callable callback;
public NetworkCallback(Callable callback) {
this.callback = callback;
}
public NetworkCallback setId(int id) {
this.id = id;
return this;
}
public int getId() {
return id;
}
public boolean execute(JSONObject data) {
try {
callback.call(); // data?
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
These were stored in a special container a created that would retain index, it's basically just an array with some helper classes. When the client gets information back it will search for the callback and then execute it.
void onMessageReceived(byte[] data) {
JSONObject json = JSONHelper.fromByteArray(data);
int callbackId = json.getInt("cbid");
if(callbackId != 0) {
callbacks.fetch(callbackId).execute(json);
}
}
The issue with this I noticed before even attempting to run the code, pondered for awhile, and ran out of things to think about. The callable class doesn't accept parameters. So, for example, say I wanted to pass a method as a callback like the following:
psuedo
method(param JSONObject data) {
print data
}
Granted this method isn't going to be the same every time it's called, so it will be created on the fly. An example in javascript of what I'm trying to achieve can be found below:
Javascript example of what I want
(function caller() {
called(function(data) {
console.log("Data: " + data);
});
})();
function called(callback) {
callback(Math.random());
}
You will want to use a Consumer for this. A consumer basically is an object on which you can call accept(data), which executes the callback.
An example:
public class Test {
public static void main(String[] args) {
Consumer consumer = new Consumer() {
#Override
public void accept(Object o) {
System.out.println(o.toString());
}
};
new Test().doSomething("Test", consumer);
}
public void doSomething(Object data, Consumer<Object> cb) {
cb.accept(data);
}
}
This prints "Test" in the console.
So I am creating a UI with JavaFX for a server controller, what it is does not matter, all that is important is that the server.getClients(); returns an ArrayList of IClients.
I wish to display these clients (they are represented by IP but once again, this doesn't seem relevant) in a ListView. However, clients may connect at any given point in time and when this happens, they get added to the server's IClient ArrayList. When this List is updated, I want the ListView to refresh and show the new client. For some reason, I simply cannot get this to work.
I am very new to JavaFX and I think I might be overseeing something.
I'm very sorry if this is a duplicate or obvious, I have searched for a long time over the past couple of days but I might have overlooked a solution.
The following code is the abbreviated version of my FXMLController for the JavaFX application:
/*imports*/
public class FXMLController implements Initializable {//serverUI.FXMLController
#FXML private ListView clientListView;
/*some more (irrelevant) code*/
private IServer server;
/*some more (irrelevant) code*/
private ObservableList<IClient> serverClientsObservableList;
#Override
public void initialize(URL url, ResourceBundle rb) {
System.out.println("initialization...");
/*some more (irrelevant) code*/
//the server was started here
// FXML Controls
initClientListView();
/*some more (irrelevant) code*/
System.out.println("initialized");
}
/*some more (irrelevant) code*/
private void initClientListView() {
System.out.println("clientListView");
serverClientsObservableList = FXCollections.observableList(server.getClients());
serverClientsObservableList.addListener(new ListChangeListener<IClient>() {
#Override
public void onChanged(ListChangeListener.Change<? extends IClient> change) {
System.out.println("list change detected");
//is any of the followin three lines really necessary to update the ListView content?
serverClientsObservableList.setAll(server.getClients());
clientListView.setItems(null);
clientListView.setItems(serverClientsObservableList);
}
});
clientListView.setItems(serverClientsObservableList);
}
/*some more (irrelevant) code*/
}
EDIT:
I don't want to refresh the ListView when something in the IClients changes, nothing changes in them. I want to refresh the ListView when a NEW IClient is ADDED to the server's client list. The ListView should show the NEW IClient
EDIT2:
According to the suggested duplicate I tried the following, however I don't really understand what it's doing and how it works. This did not solve the problem, it gives me an error when I try to create the new Observable[]
Callback<IClient, Observable[]> extractor = new Callback<IClient, Observable[]>() {
#Override
public Observable[] call(IClient c) {
return new Observable[] {c.getNameProperty()};
}
};
ObservableList<IClient> clientOList = FXCollections.observableArrayList(extractor);
Additionally: the code where I add the clients to the server.
Long story short, this is an assignment where we have to user RMI in an inverse way, the server commands the clients. Clients register themselves to the server's list and that's where they're added to the IClient list.
package serviceImplementation;
import commandService.ICommand;
import commandServiceImplementation.CommandResult;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import service.IClient;
import service.IServer;
public class ServerService extends UnicastRemoteObject implements IServer {
private ArrayList<IClient> clients;
public ServerService() throws RemoteException {
clients = new ArrayList<>();
}
#Override
public boolean register(IClient client) throws RemoteException {
if(!clients.contains(client)) {
clients.add(client);
return true;
}
return false;
}
#Override
public boolean unRegister(IClient client) throws RemoteException {
if(clients.contains(client)) {
clients.remove(client);
return true;
}
return false;
}
#Override
public String ping() throws RemoteException {
long arrival = System.currentTimeMillis();
System.out.println("Got pinged at [" + arrival + "]");
return ("server ponged [" + arrival + "]");
}
#Override
public CommandResult sendCommand(ICommand command, IClient targetClient) throws RemoteException {
if(clients.contains(targetClient)) {
return clients.get(clients.indexOf(targetClient)).executeCommand(command);
}
return null;
}
#Override
public ArrayList<IClient> getClients() {
return clients;
}
}
Your observable list is created as a wrapper for the underlying list in the ServerService using FXCollections.observableList(...). The observable list that is returned by this just wraps the underlying list, so it always contains the same elements as the underlying list. However, as noted in the documentation:
mutation operations made directly to the underlying list are not reported to observers of any ObservableList that wraps it.
When clients are registered or unregistered in the server service, you add them to the underlying list. Since the underlying list is not an observable list, no notifications are fired, and so the ListView does not know to refresh itself.
One possible solution may be to use an ObservableList in the ServerService:
public class ServerService extends UnicastRemoteObject implements IServer {
private ObservableList<IClient> clients;
public ServerService() throws RemoteException {
clients = FXCollections.observableArrayList();
}
// ...
#Override
public ObservableList<IClient> getClients() {
return clients;
}
}
and then you do
private void initClientListView() {
clientListView.setItems(server.getClients());
}
Note that this couples your ServerService to the JavaFX API; this is probably not too bad as the JavaFX Collections API does not rely on any UI elements at all.
However, the code above will not work if your clients are registered/unregistered on a background thread (i.e. not on the FX Application Thread), which is almost certainly the case. Because of this, you need to make the following changes to make this work:
#Override
public boolean register(IClient client) throws RemoteException {
FutureTask<Boolean> register = new FutureTask<>(() ->
if(!clients.contains(client)) {
clients.add(client);
return true;
}
return false;
);
Platform.runLater(register);
return register.get();
}
#Override
public boolean unRegister(IClient client) throws RemoteException {
FutureTask<Boolean> unRegister = new FutureTask<>(() ->
if(clients.contains(client)) {
clients.remove(client);
return true;
}
return false;
);
Platform.runLater(unRegister);
return unRegister.get();
}
Now your ServerService has a much stronger dependency on JavaFX, because it assume the FX Application Thread is running. You didn't make any specifications about how this is being used, but there's a good chance you don't want this coupling.
An alternative is to support callbacks in the ServerService. You can represent these pretty simply using a Consumer<IClient>. This looks something like:
public class ServerService extends UnicastRemoteObject implements IServer {
private ArrayList<IClient> clients;
private Consumer<IClient> registerCallback = client -> {} ;
private Consumer<IClient> unregisterCallback = client -> {} ;
public ServerService() throws RemoteException {
clients = new ArrayList<>();
}
public void setRegisterCallback(Consumer<IClient> registerCallback) {
this.registerCallback = registerCallback ;
}
public void setUnregisterCallback(Consumer<IClient> unregisterCallback) {
this.unregisterCallback = unregisterCallback ;
}
#Override
public boolean register(IClient client) throws RemoteException {
if(!clients.contains(client)) {
clients.add(client);
registerCallback.accept(client);
return true;
}
return false;
}
#Override
public boolean unRegister(IClient client) throws RemoteException {
if(clients.contains(client)) {
clients.remove(client);
unregisterCallback.accept(client);
return true;
}
return false;
}
#Override
public String ping() throws RemoteException {
long arrival = System.currentTimeMillis();
System.out.println("Got pinged at [" + arrival + "]");
return ("server ponged [" + arrival + "]");
}
#Override
public CommandResult sendCommand(ICommand command, IClient targetClient) throws RemoteException {
if(clients.contains(targetClient)) {
return clients.get(clients.indexOf(targetClient)).executeCommand(command);
}
return null;
}
#Override
public ArrayList<IClient> getClients() {
return clients;
}
}
and now in your UI code you do
private void initClientListView() {
System.out.println("clientListView");
serverClientsObservableList = FXCollections.observableArrayList(server.getClients());
server.setRegisterCallback(client -> Platform.runLater(() ->
serverClientsObservableList.add(client)));
server.setUnregisterCallback(client -> Platform.runLater(() ->
serverClientsObservableList.remove(client)));
clientListView.setItems(serverClientsObservableList);
}