-
Notifications
You must be signed in to change notification settings - Fork 754
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added two progress indicators to inform of the upload/download of byt… #328
Conversation
…es during the request execution
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). 📝 Please visit https://cla.developers.google.com/ to sign. Once you've signed (or fixed any issues), please reply here with What to do if you already signed the CLAIndividual signers
Corporate signers
ℹ️ Googlers: Go here for more info. |
@googlebot I signed it! |
CLAs look good, thanks! ℹ️ Googlers: Go here for more info. |
This is an interesting idea and a request we've gotten before, but I'm not sure that in practice it works as neatly as specified here, and I'm not sure it really fits into Volley's broader goals or encourages good practices. My two high-level concerns are:
I'd be open to this if there was more evidence that 1) makes sense in light of buffering, or if there was clearly value here despite the potential inaccuracy, and that there were use cases for these APIs even for requests that could reasonably be considered small. |
My objective is to improve the UX by allowing development to indicate progress in the event of a bad connection. I currently use with files of less than 1Mo (Medical inventory) and it happens that the server is limited. I provide in this way a differentiated indication between:
The user has less of the impression of waiting. I specify that I do not use it for large files. I do not want to have exact information but just to be able to improve the UX. |
Thanks for the context. It seems reasonable enough, but at the same time with all the caveats about how realistic these percentages are, I'm hesitant to expose them directly in Volley's API. I think I'd feel more comfortable keeping the scope of this smaller by providing the hooks you need to build such a listener yourself without having to support the full listener interface or try to describe the caveats such an interface might have. One way to accomplish that would be to expose the request OutputStream and response InputStream from HurlStack so that you could wrap them in your own filter streams which could monitor transfer progress and report it to your application however you wish. That is, if HurlStack had a protected method to get the InputStream for a connection whose default implementation was "new UrlConnectionInputStream(connection)", you would be able to override it and wrap the superclass implementation's InputStream in your own subclass of FilterInputStream which could override calls to read() and implement your callback that way. Is that a reasonable option for you? Then all we have to change in Volley itself is adding a couple protected methods to HurlStack for creating the input/output streams for a connection. This keeps the scope of the change smaller and avoids more common code like BasicNetwork, and doesn't expose an API that would be harder to support across all HTTP stacks and which might be inaccurate (which could be fine for you, but not others). And it feels more reasonable to say that HurlStack will always require pulling out an OutputStream to write the request body to and pulling out an InputStream to read the response body from, so allowing subclasses to tweak these implementations feels like a reasonably safe long-term API. |
…the request execution in superclass of HurlStack
Thank you for your detailed reply which clarifies the situation. That's a perfectly reasonable option for me. I've therefore withdrawn all my modifications concerning objects other than HurlStack, and I added the two protected methods as you suggested, as well as the modifications relating thereto. The arguments of these methods are "Request", "Connection", "Length" and "ResponseCode" (Output) objects that might be useful as they are to me. I hope the result suits you. |
…the request execution in superclass of HurlStack
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay. I think I support this approach at a high-level but I have some comments to resolve first.
@@ -153,10 +154,10 @@ private static boolean hasResponseBody(int requestMethod, int responseCode) { | |||
* Wrapper for a {@link HttpURLConnection}'s InputStream which disconnects the connection on | |||
* stream close. | |||
*/ | |||
static class UrlConnectionInputStream extends FilterInputStream { | |||
protected static class UrlConnectionInputStream extends FilterInputStream { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would rather not expose this implementation detail as part of Volley's API. I don't think you should need it - instead of extending it, you can build your own FilterInputStream subclass which filters the result of super.createInputStream() using composition rather than inheritance.
@@ -223,7 +239,7 @@ private HttpURLConnection openConnection(URL url, Request<?> request) throws IOE | |||
// NOTE: Any request headers added here (via setRequestProperty or addRequestProperty) should be | |||
// checked against the existing properties in the connection and not overridden if already set. | |||
@SuppressWarnings("deprecation") | |||
/* package */ static void setConnectionParametersForRequest( | |||
/* package */ static void setConnectionParametersForRequest(HurlStack stack, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than passing HurlStack through static methods, we should probably just make those methods non-static, especially if they're private/package-private helpers.
* @param length size of stream to write. | ||
* @return an DataOutputStream object for write request body. | ||
*/ | ||
protected DataOutputStream createOutputStream (Request<?> request, HttpURLConnection connection, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We shouldn't require the subclass to create DataOutputStream specifically, as that's an implementation detail. We should just have this method signature require returning an OutputStream, and default to returning connection.getOutputStream(). addBody should still wrap it in a DataOutputStream as before.
…the request execution in superclass of HurlStack
So much the better if it suits you. I was hesitant to make certain changes as you envisioned. I was not sure I could afford some changes that could have had consequences. Our visions being in agreement, I made the modifications that you asked for. |
@@ -153,7 +155,7 @@ private static boolean hasResponseBody(int requestMethod, int responseCode) { | |||
* Wrapper for a {@link HttpURLConnection}'s InputStream which disconnects the connection on | |||
* stream close. | |||
*/ | |||
static class UrlConnectionInputStream extends FilterInputStream { | |||
private static class UrlConnectionInputStream extends FilterInputStream { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can just leave this unchanged. I'm not sure if anything is using it but it shouldn't impact this PR. (I was concerned about increasing this classes visibility to protected, but it's fine to leave as is).
* @param length size of response stream. | ||
* @return an UrlConnectionInputStream object for read response. | ||
*/ | ||
protected FilterInputStream createInputStream (Request<?> request, HttpURLConnection connection, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the return type here should be InputStream, not FilterInputStream. Volley doesn't care/depend on what kind of InputStream gets returned here - any InputStream is fine. The fact that we use FilterInputStream here and in your (presumed) subclass isn't a requirement, just an implementation detail.
@@ -153,7 +155,7 @@ private static boolean hasResponseBody(int requestMethod, int responseCode) { | |||
* Wrapper for a {@link HttpURLConnection}'s InputStream which disconnects the connection on | |||
* stream close. | |||
*/ | |||
static class UrlConnectionInputStream extends FilterInputStream { | |||
private static class UrlConnectionInputStream extends FilterInputStream { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can just leave this unchanged. I'm not sure if anything is using it but it shouldn't impact this PR. (I was concerned about increasing this classes visibility to protected, but it's fine to leave as is).
@@ -289,8 +306,22 @@ private static void addBody(HttpURLConnection connection, Request<?> request, by | |||
connection.setRequestProperty( | |||
HttpHeaderParser.HEADER_CONTENT_TYPE, request.getBodyContentType()); | |||
} | |||
DataOutputStream out = new DataOutputStream(connection.getOutputStream()); | |||
OutputStream out = this.createOutputStream ( request, connection, body.length ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be:
DataOutputStream out = new DataOutputStream(createOutputStream(request, connection, body.length));
out.write(body);
out.close();
protected OutputStream createOutputStream (Request<?> request, HttpURLConnection connection, | ||
int length) throws IOException { | ||
|
||
return new DataOutputStream (connection.getOutputStream ()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return connection.getOutputStream()
* @param request current request. | ||
* @param connection current connection of request. | ||
* @param length size of stream to write. | ||
* @return an OutputStream object for write request body. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "@return an OutputStream to which the request body will be written"
} | ||
|
||
/** | ||
* Overload this method for manipulate request stream (Use 'write (byte[] b)' in subclasse). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two comments here:
-
I think the wording could be clearer. In general a Javadoc should have a one-sentence summary of what the method needs to do, and then any additional context can come in other paragraphs. For example:
"Create and return an OutputStream to which the request body will be written.
May be overridden by subclasses to manipulate or monitor this output stream."
-
My other comment is what the "Use write(byte[] b) in subclasse(s)" is referencing. What does this mean? In Volley itself, I'm assuming it's fine for us to use any valid method in OutputStream's signature to write data. We currently use write(byte[]) but I don't think we can guarantee always doing this in the future. But that's in Volley itself. In subclasses, why would you ever be calling write()?
I think what you're saying here is that subclasses can override write(byte[]) to monitor the write. If so, I don't think we should document this, because that might change (maybe we buffer the writes and do it in chunks, for example). All we guarantee is that we'll write the bytes using OutputStream. Your subclass and subclasses in general should monitor any and all methods you care about (e.g. all three write overloads) and support any of them being called.
BTW, there was an issue where our continuous build results weren't being posted to PRs. I've fixed that and rerun the build so you should see the failure now - please be sure to address that. |
If I am not mistaken, I just have to modify the HurlStackText file by replacing:
Can I do it? I take care of the last details. |
You should be able to use mHurlStack.setConnectionParametersForRequest. |
…the request execution in superclass of HurlStack
…the request execution in superclass of HurlStack
…the request execution in superclass of HurlStack
…the request execution in superclass of HurlStack
…the request execution in superclass of HurlStack
…the request execution in superclass of HurlStack
…the request execution in superclass of HurlStack
Phew, finally ... It was not without difficulty to pass 'GoogleJavaFormat', it is very picky. But I did it. Is it all good suddenly? ^^ |
- Remove redundant arguments to createInputStream which can be inferred from the provided HttpURLConnection. - Clean up Javadoc. - Add unit tests.
Sorry about the formatting hassle. For future reference, "./gradlew googleJavaFormat" does the formatting automatically for you. I think the length and response code arguments to createInputStream were redundant, because we're already passing in the connection object. If the subclass wants these values, it can just call connection.getResponseCode()/getResponseLength() directly. I removed those arguments, made some small edits to the Javadoc, and added unit tests. Merging. |
Thank you for the correction. I focused so much on the overloads that I had missed out. |
Hi,
That branch provides two listeners in order to follow up on the progress of sending and receiving data.
Thank you for your time,
Best regards,
Nicolas Dagnas