Conversation
|
Thank you - I'll have a closer look next week |
|
Could you please produce an alternative PR that uses Combine promises? I think that could be the best compromise. |
FlowCrypt/Functionality/Mail Provider/Message Gateway/GmailService+send.swift
Show resolved
Hide resolved
tomholub
left a comment
There was a problem hiding this comment.
Thanks! more questions
| private func encryptAndSendMessage() -> Promise<Bool> { | ||
| Promise<Bool> { [weak self] () -> Bool in | ||
| guard let self = self else { return false } | ||
| private func prepareMessage() -> Result<Data, SendMessageError> { |
There was a problem hiding this comment.
Got it, the futures are similar to promises. Now I understand it it a bit better.
What is this particular method - it returns Result<Data, SendMessageError>. How is that different from Future<...>?
There was a problem hiding this comment.
This is return synchronously.
In this case it's just a "helper function"
| case .success(let data): | ||
| self.messageSender.sendMail(mime: data) | ||
| .retry(3) | ||
| .sink{ [weak self] result in | ||
| guard case .failure(let error) = result else { | ||
| return | ||
| } | ||
| self?.showAlert(error: error, message: "compose_error".localized) | ||
| } receiveValue: { [weak self] _ in | ||
| self?.handleSuccessfullySentMessage() | ||
| } | ||
| .store(in: &cancellable) |
There was a problem hiding this comment.
For this demo, please replicate original behavior - without retry or other features not present in the original.
Is the switch prepareMessage() { the only way to handle a Result? Is there some other possible pattern like:
let preparedMessage = prepareMessage()
if preparedMessage.isSuccess {
} else {
}?
There was a problem hiding this comment.
Please check latest commit. Implemented similar to what we have in original code.
| }.catch(on: .main) { [weak self] error in | ||
| self?.showAlert(error: error, message: "compose_error".localized) | ||
| case .success(let data): | ||
| self.messageSender.sendMail(mime: data) |
There was a problem hiding this comment.
Does self.messageSender.sendMail have to be in the result handling code? Could it be more similar to original code, where encryptAndSendMessage took care of sending as well?
tomholub
left a comment
There was a problem hiding this comment.
I can accept using Combine futures this way, it looks good. Thank you for the demo.
We can clean up this PR to settle on a pattern, then merge it.
When converging to Combine, please don't editorialize the code at the same time. Please try to match the code structure 1:1 else it's very difficult to see if original functionality was preserved. If you notice things to refactor, if possible or unless it's really tiny, please file issues with examples for later instead of doing it in the same step.
Please do self-review of the code after you commit it, you yourself will discover a lot of the things that I'm discovering and can fix it instead of having me raise the concern (like SendMessageError which is unused and errors dropped silently - a self review on GitHub would make this very obvious).
Going forward, whenever you'd like me to look at code, please go to Files changed first yourself and go through your changes as a reviewer would. See possible problems when comparing original and new code, and adjust them if needed. Then if you made changes, do another such review until you're satisfied with the changes as they show on GitHub. Then click Finish your review, choose the Comment type and say LGTM. I also do this when I submit my own PRs, it's a way to make sure to not unnecessarily burden the reviewer with pointing out small fixes that are obvious, and instead allow the reviewer to focus on the bigger picture. Thanks!
| guard recipients.isNotEmpty else { | ||
| return | ||
| } |
There was a problem hiding this comment.
Should add back error handling, not just return and ignore. If it's input validation error from user, then we render an alert and return. If it's a nilcheck that was already supposed to be checked earlier and we're just sanity-checking our code, than that's a coding error and should result in a crash, not be ignored.
There was a problem hiding this comment.
I would suggest to use implementation with Result<SendableMessage, SendMessageError> as it was in previous commit for all this checks instead of returning true/false, and use asynchronous Future only for send message operation.
| guard let text = self.contextToSend.message else { | ||
| return |
There was a problem hiding this comment.
Should add back error handling, not just return and ignore. If it's input validation error from user, then we render an alert and return. If it's a nilcheck that was already supposed to be checked earlier and we're just sanity-checking our code, than that's a coding error and should result in a crash, not be ignored.
There was a problem hiding this comment.
Skip this part for this example, will return back alert and proper handling
| guard let myPubKey = self.dataService.publicKey else { | ||
| return | ||
| } |
There was a problem hiding this comment.
Also here bring back alert?
| guard let allRecipientPubs = getPubKeys(for: recipients) else { | ||
| return | ||
| } |
There was a problem hiding this comment.
Unsure how was this handled before, looks like we should improve it, but that would be for another PR.
There was a problem hiding this comment.
Will use same logic as it was before. This method will return true or false
| .sink( | ||
| receiveCompletion: { [weak self] result in | ||
| guard case .failure(let error) = result else { | ||
| return | ||
| } | ||
| self?.showAlert(error: error, message: "compose_error".localized) | ||
| }, | ||
| receiveValue: { [weak self] in | ||
| self?.handleSuccessfullySentMessage() | ||
| }) | ||
| .store(in: &cancellable) |
There was a problem hiding this comment.
This PR looks good, and we could actually clean up and merge it.
What does the .sink represent?
I figured receiveCompletion triggers on both error and success, and you are filtering for error only? Isn't there a more convenient method like receiveFailure?
What does the .store(in: &cancellable) do?
There was a problem hiding this comment.
sink is used to "subscribe" to stream of events.
receive completion will be triggered in case of error and in case when stream is finished
.sink(
receiveCompletion: { completion in
switch completion {
case .failure(let error): print("Error \(error)")
case .finished: print("Publisher is finished")
}
}
...
In this example with Future(promise), there will be only 1 value, which can be accessed in receiveValue: closure and 1 receiveCompletion.
But in cases with other publishers, receiveValue closure can be called multiple times but receiveCompletion - will be called only once when publisher finished or failed
There is sink(receiveValue:) function, but there is no just receiveError.
.store(in: &cancellable).
In case we started some long going request and left the screen cancellable will frees up any allocated resources and also will stop side effects.
For example user start fetching message with bad internet connection and was waiting for few seconds. Then taped back and left the screen, cancelable will terminate this execution and free up resources.
There was a problem hiding this comment.
All subscriptions should be stored in cancellable to prevent their execution if view controller/service is deallocated
| // temporary disable search contacts - https://github.com/FlowCrypt/flowcrypt-ios/issues/217 | ||
| // showScopeAlertIfNeeded() | ||
|
|
||
| cancellable.forEach { $0.cancel() } |
|
This PR, as title says is just an example of Combine usage. It doesn't meant to be reviewed at all. |
I get it, it was only meant as a demonstration. Demonstration succeeded - can we bring the PR from its current state to a mergeable one, so that it's not wasted effort? To begin work to get rid of google Promise library. Please also help me by answering the questions above. |
|
Sure, thanks. On my way with answers |
|
Initially, only Future for minimal changes in code structure. then when
done, we will do a similar demo like this, for other Combine functionality
where it makes most sense. Then if it looks good, start using that too.
…On Wednesday, July 14, 2021, Anton Kharchevskyi ***@***.***> wrote:
Sure, thanks. On my way with answers
Are we going to use only Future from Combine? or we are free to use
Combine itself?
—
You are receiving this because your review was requested.
Reply to this email directly, view it on GitHub
<#306 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ABQDZEP3MP5DGQZB7LRIRHDTXWYV5ANCNFSM44H4WYIA>
.
--
--
Tom James Holub <http://holub.me/>
|
|
closing in favor of #400 |
This is an example for #290 of how we can use Combine without huge changing of our codebase.
Please take a look on some improvements which can be added with using Combine