RxAndroid: Retrofit doesn't work on the second time? - retrofit

I am using RxAndroid and Retrofit for httpRequest, my code is as below:
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(logging)
.authenticator(new DigestAuthenticator())
.build();
// retrofit with custom client
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(NetUtil.getServerBaseUrl())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(client)
.build();
ApiService apiService = retrofit.create(ApiEndpoints.class);
CustomerSubscriber mSubscriber = new CustomerSubscriber();
apiService.checkAccount(mUser.getEmail()).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.newThread()).subscribe(mSubscirber);
//Retrofit
#Headers({
"Accept: application/json;charset=utf-8",
"Content-Type: application/json;charset=utf-8"
})
#HEAD("/account")
Observable<Result<Void>> rxHeadAccount(
#Query("email") String encryptedEmail);
My scenario like this: every time when I click the button "apiService.checkAccount()" method will be triggered. The first time:click button, the httpRequest is sent and I can get response
However, click the second time, no httpRequest was sent and the logs shows " I/Choreographer: Skipped 115 frames! The application may be doing too much work on its main thread."
However if I change code to
apiService.checkAccount(mUser.getEmail()).observeOn(AndroidSchedulers.mainThread()).subscribeOn(Schedulers.newThread()).subscribe(new Subscriber<Result<Void>>() {
#Override
public void onCompleted() {
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(Result<Void> voidResult) {
}
});
}
});
The Retrofit works very well, it can send HttpRequest as many times as your want?
Looks like the "Subscriber" can't be one instance?
Why, Any idea about it?
Thanks

Once an observable emits a terminal event (a complete or error event) it is not going to emit any more events (see here). Each call to apiService.checkAccount(mUser.getEmail()) creates a new observable to which you can subscribe with a subscriber. This creates a subscription.
You probably want to store your subscription objects and unsubscribe from them when you're done.

Related

Blazor WebAssembly SignalR HubConnection causing javascript error on reload

I have a SignalR HubConnection within my Blazor WebAssembly application and whilst it works most of the time, if I reload the page (via the browser reload) then I often am getting the following error in the console and the connection is not made:
Uncaught Error: The delegate target that is being invoked is no longer available. Please check if it has been prematurely GC'd.
at Object.invoke_delegate (dotnet.5.0.4.js:1)
at WebSocket. (dotnet.5.0.4.js:1)
Here's a rough, simplified view of the code where I create the HubConnection (and dispose it).
#inherits LayoutBase
#attribute [Authorize]
<AuthorizeView>
<Authorized>
//...
</Authorized>
<NotAuthorized>
//...
</NotAuthorized>
</AuthorizeView>
public class LayoutBase : LayoutComponentBase, IAsyncDisposable
{
[Inject] public IAccessTokenProvider AccessTokenProvider { get; set; }
private readonly HubConnection _hubConnection;
protected override async Task OnInitializedAsync()
{
_hubConnection = new HubConnectionBuilder()
.AddNewtonsoftJsonProtocol(c =>
{
//...
})
.WithUrl(notificationHubUrl, option => option.AccessTokenProvider = GetAccessToken)
.WithAutomaticReconnect()
.Build();
_hubConnection.Closed += HubConnectionOnClosed;
_hubConnection.Reconnected += HubConnectionOnReconnected;
_hubConnection.Reconnecting += HubConnectionOnReconnecting;
await _hubConnection.StartAsync()
await base.OnInitializedAsync();
}
private async Task<string> GetAccessToken()
{
var tokenResult = await AccessTokenProvider.RequestAccessToken(...)
// etc...
}
// .. Event Handlers
public ValueTask DisposeAsync()
{
_logger.LogInformation($"Disposing Hub: {_hubConnection.ConnectionId}");
_hubConnection.Closed -= HubConnectionOnClosed;
_hubConnection.Reconnected -= HubConnectionOnReconnected;
_hubConnection.Reconnecting -= HubConnectionOnReconnecting;
return _hubConnection.DisposeAsync();
}
}
Previously I had it as an injected service but I eventually simplified it to this structure but it continues to get this error on reload. It's not every time I reload but most times.
I have tried changing the dispose pattern without success. I can't find any information on the error anywhere else.
Any ideas?
I don't have a definitive answer as to the underlying reason but I suspect that this is a bug somewhere in the SignalR/dotnet framework resulting in the GCing of a delegate because something drops a reference to it.
One way I've managed to provoke this error reasonably consistently is to have a handler returning just a Task, e.g.
_hubConnection.On<TEvent>(eventType.Name, OnEvent);
where OnEvent looks like this:
// THIS IS THE BROKEN SIGNATURE - DO NOT USE
private async Task OnEvent<TEvent>(TEvent e)
{
}
A workaround which appears to have fixed it for me is to make the handler actually return something. This seems to make something deeper in the framework hold a reference for longer so that it doesn't get GC'ed. E.g.
// WORKS ON MY MACHINE - Note the return type of Task<object>
private async Task<object> OnEvent<TEvent>(TEvent e)
{
// ... Do stuff
return null;
}

Microsoft ASP.NET WebHooks custom receiver gets multiple attempts

I have implemented a custom receiver for Microsoft ASP.NET WebHooks by implementing WebHookHandler.
public class Web_WebHookHandler : WebHookHandler
{
public Web_WebHookHandler()
{
this.Receiver = CustomWebHookReceiver.ReceiverName;
}
public override Task ExecuteAsync(string generator, WebHookHandlerContext context)
{
SendNotification();
return Task.FromResult(true);
}
private void SendNotification()
{
Task.Factory.StartNew(() => {
// doing some processing
});
}
}
Whenever some event gets fired, it hits my above receiver 3 times. I have tried everything but nothing made any difference. Please help me sort it out.
Try adding bellow code in the ExecuteAsync before return. .i.e.
context.Response = new System.Net.Http.HttpResponseMessage (System.Net.HttpStatusCode.Gone);
return Task.FromResult(true);
Actually webhooks dispatcher inspects response from your receiver and retries if proper response is not sent back. So in order to tell dispatcher that request has been processed and everything is okay, you need to set context.Response and also return Task.FromResult(true).
Otherwise it will keep trying for 3 times atleast.

Regulate network calls in SyncAdapter onPerformSync

I m sending several retrofit calls via SyncAdapter onPerformSync and I m trying to regulate http calls by sending out via a try/catch sleep statement. However, this is blocking the UI and will be not responsive only after all calls are done.
What is a better way to regulate network calls (with a sleep timer) in background in onPerformSync without blocking UI?
#Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
String baseUrl = BuildConfig.API_BASE_URL;
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build();
service = retrofit.create(HTTPService.class);
Call<RetroFitModel> RetroFitModelCall = service.getRetroFit(apiKey, sortOrder);
RetroFitModelCall.enqueue(new Callback<RetroFitModel>() {
#Override
public void onResponse(Response<RetroFitModel> response) {
if (!response.isSuccess()) {
} else {
List<RetroFitResult> retrofitResultList = response.body().getResults();
Utility.storeList(getContext(), retrofitResultList);
for (final RetroFitResult result : retrofitResultList) {
RetroFitReview(result.getId(), service);
try {
// Sleep for SLEEP_TIME before running RetroFitReports & RetroFitTime
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
}
RetroFitReports(result.getId(), service);
RetroFitTime(result.getId(), service);
}
}
}
#Override
public void onFailure(Throwable t) {
Log.e(LOG_TAG, "Error: " + t.getMessage());
}
});
}
}
The "onPerformSync" code is executed within the "SyncAdapterThread" thread, not within the Main UI thread. However this could change when making asynchronous calls with callbacks (which is our case here).
Here you are using an asynchronous call of the Retrofit "call.enqueue" method, and this has an impact on thread execution. The question we need to ask at this point:
Where callback methods are going to be executed?
To get the answer to this question, we have to determine which Looper is going to be used by the Handler that will post callbacks.
In case we are playing with handlers ourselves, we can define the looper, the handler and how to process messages/runnables between handlers. But this time it is different because we are using a third party framework (Retrofit). So we have to know which looper used by Retrofit?
Please note that if Retrofit didn't already define his looper, you
could have caught an exception saying that you need a looper to
process callbacks. In other words, an asynchronous call needs to be in
a looper thread in order to post callbacks back to the thread from
where it was executed.
According to the code source of Retrofit (Platform.java):
static class Android extends Platform {
#Override CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
if (callbackExecutor == null) {
callbackExecutor = new MainThreadExecutor();
}
return new ExecutorCallAdapterFactory(callbackExecutor);
}
static class MainThreadExecutor implements Executor {
private final Handler handler = new Handler(Looper.getMainLooper());
#Override public void execute(Runnable r) {
handler.post(r);
}
}
}
You can notice "Looper.getMainLooper()", which means that Retrofit will post messages/runnables into the main thread message queue (you can do research on this for further detailed explanation). Thus the posted message/runnable will be handled by the main thread.
So that being said, the onResponse/onFailure callbacks will be executed in the main thread. And it's going to block the UI, if you are doing too much work (Thread.sleep(SLEEP_TIME);). You can check it by yourself: just make a breakpoint in "onResponse" callback and check in which thread it is running.
So how to handle this situation? (the answer to your question about Retrofit use)
Since we are already in a background thread (SyncAdapterThread), so there is no need to make asynchronous calls in your case. Just make a Retrofit synchronous call and then process the result, or log a failure. This way, you will not block the UI.

Is it possible to run a Retrofit observable synchronously?

I'm trying to migrate my app to work with RxJava.
I already use Retrofit and therefore I'm trying to use a Retrofit interface which methods return Observables.
However I'm now having issues with coding tests against it, as I can't get the Observable to run on the main thread; I'm trying to use Scheduler.immediate() for it.
It seems that Retrofit doesn't allow to override it's behaviour, which makes totally sense for the real execution flow, but it makes testing very difficult.
As I've just started with RxJava + Retrofit I just hope I'm doing something wrong instead.
Below is what the code looks like:
#Test
public void shouldCompleteRequest() {
SomeRestRequest request = new SomeRestRequest(arg1, arg2);
TestSubscriber<SomeRestResponse> testSubscriber = new TestSubscriber<>();
new SomeRestCommand(mRestApi,
arg1, arg2
Schedulers.immediate(),
Schedulers.immediate(),
mMockEventBus).execute(request, testSubscriber);
testSubscriber.assertCompleted();
}
where
public void execute(T request, Observer<S> observer) {
getCommand(request)
.observeOn(mObserveOnScheduler) //The test injects Schedulers.immediate()
.subscribeOn(mSubscribeOnScheduler) //The test injects Schedulers.immediate()
.subscribe(observer);
}
,
#Override
protected Observable<SomeRestResponse> getCommand(SomeRestRequest request) {
return mRestApi.restCommand(arg1, arg2);
}
and
public interface RestApi {
#GET("/someEndPoint")
Observable<SomeRestResponse> restCommand(#Query("arg1") String arg1, #Query("arg2") String arg2);
}
If you modify your test to add testSubscriber.awaitTerminalEvent();, then your test will wait for the call to complete and hence the test will pass. You will still have to do an assertCompleted() as the terminal event can be either of successful completion or error.
#Test
public void shouldCompleteRequest() {
SomeRestRequest request = new SomeRestRequest(arg1, arg2);
TestSubscriber<SomeRestResponse> testSubscriber = new TestSubscriber<>();
new SomeRestCommand(mRestApi,
arg1, arg2
Schedulers.immediate(),
Schedulers.immediate(),
mMockEventBus).execute(request, testSubscriber);
testSubscriber.awaitTerminalEvent(); // add this line here
testSubscriber.assertCompleted();
}
I looked up the source code of Retrofit 1.9.0 and as per RxSupport class, the call is always executed in a separate thread provided by the httpExecutor. Hence using Schedulers.immediate() did not cause the call to happen in the main thread.

Are there any restrictions in writing multiple http responses?

I am building a HTTP proxy with netty, which supports HTTP pipelining. Therefore I receive multiple HttpRequest Objects on a single Channel and got the matching HttpResponse Objects. The order of the HttpResponse writes is the same than I got the HttpRequest. If a HttpResponse was written, the next one will be written when the HttpProxyHandler receives a writeComplete event.
The Pipeline should be convenient:
final ChannelPipeline pipeline = Channels.pipeline();
pipeline.addLast("decoder", new HttpRequestDecoder());
pipeline.addLast("encoder", new HttpResponseEncoder());
pipeline.addLast("writer", new HttpResponseWriteDelayHandler());
pipeline.addLast("deflater", new HttpContentCompressor(9));
pipeline.addLast("handler", new HttpProxyHandler());
Regarding this question only the order of the write calls should be important, but to be sure I build another Handler (HttpResponseWriteDelayHandler) which suppresses the writeComplete event until the whole response was written.
To test this I enabled network.http.proxy.pipelining in Firefox and visited a page with many images and connections (a news page). The problem is, that the browser does not receive some responses in spite of the logs of the proxy consider them as sent successfully.
I have some findings:
The problem only occurs if the connection from proxy to server is faster than the connection from proxy to browser.
The problem occurs more often after sending a larger image on that connection, e.g. 20kB
The problem does not occur if only 304 - Not Modified responses were sent (refreshing the page considering browser cache)
Setting bootstrap.setOption("sendBufferSize", 1048576); or above does not help
Sleeping a timeframe dependent on the responses body size in before sending the writeComplete event in HttpResponseWriteDelayHandler solves the problem, but is a very bad solution.
I found the solution and want to share it, if anyone else has a similar problem:
The content of the HttpResponse is too big. To analyze the content the whole HTML document was in the buffer. This must be splitted in Chunks again to send it properly. If the HttpResponse is not chunked I wrote a simple solution to do it. One needs to put a ChunkedWriteHandler next to the logic handler and write this class instead of the response itself:
public class ChunkedHttpResponse implements ChunkedInput {
private final static int CHUNK_SIZE = 8196;
private final HttpResponse response;
private final Queue<HttpChunk> chunks;
private boolean isResponseWritten;
public ChunkedHttpResponse(final HttpResponse response) {
if (response.isChunked())
throw new IllegalArgumentException("response must not be chunked");
this.chunks = new LinkedList<HttpChunk>();
this.response = response;
this.isResponseWritten = false;
if (response.getContent().readableBytes() > CHUNK_SIZE) {
while (CHUNK_SIZE < response.getContent().readableBytes()) {
chunks.add(new DefaultHttpChunk(response.getContent().readSlice(CHUNK_SIZE)));
}
chunks.add(new DefaultHttpChunk(response.getContent().readSlice(response.getContent().readableBytes())));
chunks.add(HttpChunk.LAST_CHUNK);
response.setContent(ChannelBuffers.EMPTY_BUFFER);
response.setChunked(true);
response.setHeader(HttpHeaders.Names.TRANSFER_ENCODING, HttpHeaders.Values.CHUNKED);
}
}
#Override
public boolean hasNextChunk() throws Exception {
return !isResponseWritten || !chunks.isEmpty();
}
#Override
public Object nextChunk() throws Exception {
if (!isResponseWritten) {
isResponseWritten = true;
return response;
} else {
HttpChunk chunk = chunks.poll();
return chunk;
}
}
#Override
public boolean isEndOfInput() throws Exception {
return isResponseWritten && chunks.isEmpty();
}
#Override
public void close() {}
}
Then one can call just channel.write(new ChunkedHttpResponse(response) and the chunking is done automatically if needed.

Resources