-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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 support for taking snapshots on Android #625
Added support for taking snapshots on Android #625
Conversation
# Conflicts: # android/src/main/java/com/airbnb/android/react/maps/AirMapModule.java
Additionally, I'd like to make a proposal for a new and improved const promise = map.takeSnapshot({
width: 100, // optional, when omitted the view width is used
height: 100, // optional, when omitted the view height is used
result: 'file', // values: 'file', 'base64' (default: 'file')
format: 'png', // file-format: 'png', 'jpg' (default: 'png')
quality: 0.8, // compression-quality (only relevant for jpg) (default: 1.0)
}); The result of the promise is a file-uri when And for backwards compatibility the function can be used the old way: map.takeSnapshot(300, 200, region, (err, result) => {
...
}); I'd like to hear your thoughts on this. cheers, Hein |
This is awesome, great! I'm not super excited about the forking between iOS & Android, but it's opaque to the end developer so that's fine. I like your new new API. We can introduce that with deprecation warning and then cut over to the new API in the subsequent breaking release. |
// Save the snapshot to disk | ||
OutputStream outputStream = null; | ||
try { | ||
if ("file".equals(result)) { |
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.
Perhaps we can pull these strings out to CONSTANTs?
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.
DONE!
if (typeof width === 'object') { | ||
options = width; | ||
} else { | ||
options = { |
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.
Can you add a deprecation warning for this case? That way we can remove the old API in the next breaking release.
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.
Done. I've updated the function so that it uses the old API when 4 arguments are specified (and only when on iOS). In all other cases the new API is used.
The deprecation message is generated using console.warn
.
@spikebrehm Good calls! I'm gonna make those changes and let you know. What exactly did you mean by "forking between iOS & Android" ? |
# Conflicts: # components/MapView.js # ios/AirMaps/AIRMapManager.m
Alright, I've implemented the new takeSnapshot API. I've updated the iOS code to support optional The API now looks like this:
|
Looks great! Should we try and get this merged in for the next release ? |
Any chance we can get this in for the next release? |
/cc @felipecsl You good with this? |
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.
@christopherdro thanks for the heads up. I left some comments but overall looks good. Can approve once they are addressed!
try { | ||
outputStream.close(); | ||
} catch (IOException e) { | ||
e.printStackTrace(); |
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.
remove this line, just ignore the exception
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.
you could use closeQuietly
here too
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.
Thanks so much for all the feedback @felipecsl . I will update the code accordingly and re-test and the update the PR. Hopefully this week, but probably next week.
Cheers, Hein
outputStream = null; | ||
byte[] bytes = ((ByteArrayOutputStream) outputStream).toByteArray(); | ||
String data = Base64.encodeToString(bytes, Base64.NO_WRAP); | ||
promise.resolve(data); |
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.
it would be nice if you could break down this logic into smaller methods. maybe takeBase64Snapshot()
and takeFileSnapshot()
?
return; | ||
} | ||
view.map.snapshot(new GoogleMap.SnapshotReadyCallback() { | ||
public void onSnapshotReady(Bitmap snapshot) { |
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.
annotate with @Nullable
if (snapshot != null && width != 0 && height != 0 && (width != snapshot.getWidth() || height != snapshot.getHeight())) { | ||
snapshot = Bitmap.createScaledBitmap(snapshot, width, height, true); | ||
} | ||
if (snapshot == null) { |
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.
invert the order of these 2 if
s
File tempFile = File.createTempFile("AirMapSnapshot", "." + format, context.getCacheDir()); | ||
outputStream = new FileOutputStream(tempFile); | ||
snapshot.compress(compressFormat, (int)(100.0 * quality), outputStream); | ||
outputStream.close(); |
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.
close()
can throw IOException
. A common pattern is to create a method closeQuietly()
that looks like this:
public static void closeQuietly(Closeable closeable) {
if (closeable == null) return;
try {
closeable.close();
} catch (IOException ignored) {
}
}
promise.resolve(data); | ||
} | ||
} | ||
catch (Exception e) { |
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.
what exceptions are you catching exactly? can you be more specific or narrow down its scope?
Hi, it look a little longer but I've updated the code and addressed all the issues you pointed out. |
Thanks, will take a look soon. |
Awesome, thanks @spikebrehm |
@spikebrehm Any idea when the next release will be? |
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 added some non-blocking comments. I'll merge now and feel free to follow up.
Sorry for the delay!!! We've been very busy with the release of the new Airbnb Trips product.
this._runCommand('takeSnapshot', [ | ||
width || 0, | ||
height || 0, | ||
region || {}, |
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 region logic used to be:
const finalRegion = region || this.props.region || this.props.initialRegion;
Shouldn't we preserve that here?
* @param [config.region] Region to render (Only supported on iOS). | ||
* @param [config.format] Encoding format ('png', 'jpg') (default: 'png'). | ||
* @param [config.quality] Compression quality (only used for jpg) (default: 1.0). | ||
* @param [config.result] Result format ('file', 'base64') (default: 'file'). |
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.
Maybe add legacy
here for now?
quality: args.quality || 1.0, | ||
result: args.result || 'file', | ||
}; | ||
if ((config.format !== 'png') && |
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.
Consider pulling these out to constants at the top of the file, like:
const VALID_FORMATS = ['png', 'jpg'];
const VALID_RESULTS = ['file', 'base64'];
// ...
if (!VALID_FORMATS.include(config.format)) {
throw new Error('Invalid format specified');
}
Also it may be nice to use the invariant
package like:
invariant(VALID_FORMATS.include(config.format), 'Invalid format specified');
Published as v0.12.2 |
Awesome, thanks so much @spikebrehm ! |
This pull request adds support for
takeSnapshot
on Android.The takeSnapshot function was purposefully implemented on the AirMapModule class and not as a command on AirMapManager. The reason was that with using commands it is not possible to call the callback that is provided. The callback cannot be extracted from
ReadableMap
. This made it unsuitable for this specific function. I've taking inspiration fromreact-native-view-shot
and was able to implement the functionality on the AirMapModule instead.The region argument is not used on Android and the width and height are interpreted differently. This is because the
GoogleMaps.snapshot
doesn't take the size or region as input. This creates a feature difference with the iOS version.Additionally, I've made
takeSnapshot
promise aware. This means it now returns a promise with the resulting snapshot value (the uri) (or rejects the promise in case of an error).Android screenshot: