smartdevicelink / sdl_evolution

Tracking and proposing changes to SDL's public APIs.
https://smartdevicelink.github.io/sdl_evolution/
BSD 3-Clause "New" or "Revised" License
33 stars 122 forks source link

[Returned for Revisions] SDL 0201 - High level interface: Overlay Controllers #606

Closed jordynmackool closed 5 years ago

jordynmackool commented 5 years ago

Hello SDL community,

The review of "SDL 0201 - High level interface: Overlay Controllers" begins now and runs through February 19, 2019. The proposal is available here:

https://github.com/smartdevicelink/sdl_evolution/blob/master/proposals/0201-high-level-interface-overlay-controllers.md

Reviews are an important part of the SDL evolution process. All reviews should be sent to the associated Github issue at:

https://github.com/smartdevicelink/sdl_evolution/issues/606

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of SDL. When writing your review, here are some questions you might want to answer in your review:

More information about the SDL evolution process is available at

https://github.com/smartdevicelink/sdl_evolution/blob/master/process.md

Thank you, Jordyn Mackool

Program Manager - Livio jordyn@livio.io

joeljfischer commented 5 years ago

Hi, I'm happy to see this has moved to review. There's much that I agree with here, but I do have a number of notes:

  1. The name of "overlay controller" may be misleading. When doing the review of the Core Certification guidelines, it was argued that some of these operations may not be done in an overlay style like Ford's Gen 3 (specifically, I think AudioPassThru was called out, as it's difficult to imagine others occurring without an overlay, though they could replace the template wholesale as well). I believe I originally criticized the name "Modal" for this very reason, however, I think it's better than "Overlay," and would prefer it if nothing better can be provided. At least "Modal" is describing various "modes of operation" rather than a UI overlay, which may or may not happen based on the OEM implementation.

  2. I'm not sure that storing pending overlay controllers is better than just failing when one is already presented. I can see both being useful at different times, and the dev could implement either manually if they wanted it. Personally, I think I'd prefer failing. 🤷‍♂️ I've re-read the paragraph describing this a few times, but I think I'm still missing the purpose of allowing it, could you elaborate?

  3. When a view controller wants to present an overlay controller it must call the presentOverlayController:completion: method of the VC manager. The presenting VC must be listed in the view controller stack but does not need to be the top view controller.

I'm not sure if this is the best policy. Isn't presenting an overlay from somewhere other than the top VC on the stack more likely to lead to bugs than to be useful? Apple does allow this in my testing, however, they log a message to the console whenever it happens:

Presenting view controllers on detached view controllers is discouraged

If really needed, a VC can already grab the VC from the top of the stack and send it a message, right? Can we cut off this possible avenue for bugs?

  1. Should the method - (void)presentOverlayController:(SDLOverlayController *)overlayController completion:(SDLOverlayControllerCompletionHandler)completion; be on the UIViewController equivalent as it is in UIKit?

  2. If the overlay controller has detected the end of the presentation it should notify the VC manager. This can also be done through an encapsulated overlay controller completion handler.

How would this work? With at least some of these (such as Alert), the AlertResponse is sent immediately, not when the overlay closes. Would it need an internal timer set to the timeout and hope they line up, plus listeners for the soft buttons since the alert closes once one is hit?

  1. As it inherits SDLViewController it is possible to manipulate the head unit during a presentation. To do that the app developer must create a new view and place it in the view property of the overlay controller.

I'm not sure I'm understanding this point. It's possible to manipulate the view controller underneath the overlay? Or to manipulate the overlay?

  1. Depending on the subclass (and the min/max values of the parameter) the duration value will be ignored if it exceeds the value range.

I agree with points 1 & 2, however, might it be better that if it's set to 0 (or not set at all due to an initializer not including that parameter) to set it to the default value? e.g. for ScrollableMessage to set it to 30 seconds instead of 65.535 seconds?

  1. I would vastly prefer to see duration (and all other properties on the subclasses) be readonly to set developer expectations about changing it during presentation as well as to prevent bugs from us. We certainly will not be expecting them to change properties during presentation and it could cause significant issues that we don't want to have to account for. This is how many objects are designed in the SDL iOS library (e.g. SDLChoiceSet for the Choice Set Manager). There can be an initializer that takes the duration, or not and it's set to the default.

The main reason for this desire is these SDL UI controllers work fundamentally differently than UIKit ones. When you set those properties on UIKit, the values change on screen while it's on screen, with SDL, that will not happen. To help set expectations, I would prefer developers to understand they can set these values once. If they want something different, they need to make a new object.

  1. SDLAlertController buttons should perhaps note that it should fail to initialize anything with buttons containing more than one state?

  2. @property (nonatomic, copy) (^onAudioData)(NSData *audioData);

The name of this block and corresponding initializer names probably shouldn't include on, since that's an SDL RPC paradigm and not an iOS one? Perhaps audioDataProcessor, or audioDataHandler?

  1. Is @property (nonatomic, nonnull, copy) SDLAudioPassThruCapabilities *capabilities; intended to be nullable?

  2. Am I crazy if I think muteAudioSource should actually default to true? The vast majority of the time the developer will prefer to pause the audio to make sure they get clear voice, right?

  3. I know I've made this point several times, but I'd be remiss not to note it in the review: I strongly disagree with the use of the value property of the SDLSliderController and SDLValuePickerController. I understand that's how UIKit does it, but the SDL version works entirely differently. A UISlider is an interface item in UIKit, not a time-limited overlay controller.

The slider's value therefore ought to be a value, as nothing else makes sense. We cannot KVO observe the slider value over time as we can in UIKit. However, because our Sliders are time-limited controllers, they ought to pass back the selected value in a completion handler style block, not require the developer to remember to check the value on the view controller after the response occurs. In the current proposal, there's no way to know except by repeatedly checking the value in a timed loop or checking in the SDLOverlayControllerCompletionHandler which doesn't even pass back the view controller, so they'd have to store it themselves or even if it was modified to include it, they'd have to cast it themselves.

It just makes sense to have a completion handler here that returns the value.

  1. I don't think minimumValue and maximumValue make much sense here. They can set it to 25 and 50 but those values won't be reflected on the overlay, so what's the point?

What I'm thinking of for these classes is something like this:

@interface SDLSliderController : SDLOverlayController

@property (nonatomic, nonnull, copy) NSString *title;
@property (nonatomic, nullable, copy, readonly) NSString *message;

// must not exceed "maxvalue" of "Slider.numTicks"
@property (nonatomic, readonly) NSInteger numValues;

// maps to "Slider.position"
@property (nonatomic, readonly) NSInteger initialIndex;

// Passes back the selectedIndex
@property (nonatomic, readonly) SDLSliderCompletionHandler completionHandler;

- (instancetype)initWithTitle:(nonnull NSString *)title numPositions:(NSInteger)numPositions initialPosition:(NSInteger)initialPosition completionHandler:(SDLSliderCompletionHandler)completionHandler;

- (instancetype)initWithTitle:(nonnull NSString *)title message:(nonnull NSString *)message numPositions:(NSInteger)numPositions initialPosition:(NSInteger)initialPosition completionHandler:(SDLSliderCompletionHandler)completionHandler;

@end

@interface SDLValuePickerController : SDLOverlayController

@property (nonatomic, nonnull, copy, readonly) NSString *title;

@property (nonatomic, nonnull, copy, readonly) NSArray<NSString *> *valueLabels;

@property (nonatomic, readonly) NSInteger initialIndex;

// Passes back the selectedIndex (NSInteger) and selectedValue (NSString)
@property (nonatomic, readonly) SDLValuePickerCompletionHandler completionHandler;

- (instancetype)initWithTitle:(nonnull NSString *)title valueLabels:(nonnull NSArray<NSString *> *)valueLabels initialIndex:(NSInteger)initialIndex completionHandler:(SDLValuePickerCompletionHandler)completionHandler;

@end

something like that.

  1. SDLOverlayControllerCompletionHandler should include the SDLOverlayViewController to which it corresponds.
kshala-ford commented 5 years ago

I don't think it makese sense to discuss all items in this chat. I think a workshop over phone makes the most sense. After the workshop we can share the minutes with conclusions in this chat.

jordynmackool commented 5 years ago

The Steering Committee deferred this proposal on 2018-10-23 based on the request from the author to schedule a workshop in this comment to further discuss the feedback that was given in this comment. It was concluded that additional next steps will be determined after the workshop takes place and the meeting minutes are shared with the SDLC Members.

jordynmackool commented 5 years ago

The Steering Committee had a workshop regarding this proposal on 2018-11-14. The author is to make changes based on conclusion of the meeting minutes that was sent out on 2018-11-19 and resubmit to be reviewed.

joeljfischer commented 5 years ago
  1. I think this part of the proposal still needs additional clarification within the proposal:

Main screen overlay As it inherits SDLViewController it is possible to manipulate the head unit during a presentation. To do that the app developer must create a new view and place it in the view property of the overlay controller.

What does this really mean? Does this mean that a new template will be presented prior to the overlay being presented (e.g. a loading template) as discussed in the workshop? If so, how are you mitigating the large downsides of such an approach as discussed in that same workshop? If it's manipulating the existing view, how is that happening with a new SDLViewController? I think this section needs to be fleshed out considerably.

  1. SDLOverlayController should not have any publicly accessible initializers. Instead, the subclasses should include duration only on their own initializers (as you do currently).

  2. On SDLSliderController, if the message maps to sliderFooter (it's a bit unclear), then perhaps message should be named footerMessage or footerText instead, for clarity?

  3. We discussed in the workshop that having the minimumValue and maximumValue might not be best for this overlay, because those two values won't actually mean anything (they won't be reflected on the UI) which would be confusing for developers. I see that you noted that the position values would be mapped and displayed on the UI:

If the app does not provide a message the overlay controller creates localized numbers to translate position value to the user facing value using NSNumberFormatter and localizedStringFromNumber:numberStyle:.

However, I think this conflicts with what you said in other places:

As per mobile API the property Slider.sliderFooter is an array used for two purposes. Either it's a footer text (single item) or a list of names representing slider values (number of items must match .numTicks). This controller is used for the "single item" case.

The message property maps to Slider.sliderFooter as a singleton array. As per mobile API this will result in a descriptive message explaining the meaning of the slider value.

There is no (and can be no) footerText (or equivalent) property on SDLSliderController because the footerText is automatically being handled to show the footer number values. Also, wouldn't message map to sliderHeader.

  1. I believe SDLValuePickerController is missing a message parameter to map to sliderHeader.

  2. What was the result of your thoughts on how to retrieve the value from SDLSliderController and SDLValuePickerController? Did you decide to stick with referencing the controller within its own completion handler, which could lead to a potential retain cycle?

kshala-ford commented 5 years ago

Regarding 1. Main screen overlay

My apologies. I have to admit that I did not spend enough time on this item. Below text were notes/thoughts but I have missed to spec them out for the propoal.

I was thinking about adding an opaque flag (non-opaque is by default) to views that will solve all the "large downsides" discussed in the workshop. This flag should allow views from the underlying view controller to stay visible while the overlay controller is presenting. The opaque mode is view type related.

Top view controller's view hierarchy (all views are non-opaque): view

App presents following overlay controller (present list of albums) with view hierachy: view (opaque)

The resulting screen will be:

four lines of text ("loading", "artist", "album", "genre") because root view and text view are opaque one button ("cancel") because button view is non-opaque primary graphic because overlay's root view is opaque an no image view is contained.

The big pros are:

I understand that documentation is missing. It'll need much more text for explanation and of course a well written page in developer guides with examples. Therefore this proposal should be returned for revisions.

Regarding 2. base class accessibility

I disagree. Unfortunately there's no "protected" access modifier or abstract classes in objc which would perfectly fit but I do want to keep possibilities for customized overlays created by app partners or providers of utility code/libraries. Your suggestion is too restricting with no benefit.

Regarding 3. message/slider footer

I disagree. It's a bit unclear as you knwo the mobile API so well. I understand your point but what I'm trying to do here is to redesign all overlays and unify them as good as possible. Just as any other overlay message properties Slider.sliderFooter is also multi line capable. I don't think header or footer are really good names at all as they are too specific to the purpose. In my backlog I wanted to write a proposal to add title to Alert based on the overlays and your template title proposal. With all that I think title and message fits best.

Regarding 4. min/max values

I'm not sure if I can get your point. If priority is not clear: Internally message is of higher priority than automatically generated numbers which fits to what I wrote "If the app does not provide a message ...". Either a message or numbers will be visible.

Do you want a pure value based slider controller? Do you want to force presenting numbers? This approach removes the ability of single message sliders but does not give any benefit. Do you really want to enforce apps for reduced functionality?

Regarding 5. value picker message

Depends on 4. Again I want to note that we will loose the ability of single message sliders.

Regarding 6. the actual value

So the value properties are read only to the app developer but internally they can be set to the response's value.

Regarding retain cycles I want to refer to https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html and https://developer.apple.com/documentation/uikit/uiviewcontroller/1621380-presentviewcontroller

I don't see a retain cycle for temporarily presented overlays.

joeljfischer commented 5 years ago

Previous Questions

Regarding 1. Main screen overlay

I think I understand what you're saying, but I'm not 100% confident, so I don't want to comment too much on this. I noted the cancel soft button, which I'm assuming was a mistake because we can't currently cancel an overlay before or while it's presented without user input, right? Unless I'm not understanding correctly.

Regarding 2. base class accessibility I disagree. Unfortunately there's no "protected" access modifier or abstract classes in objc which would perfectly fit but I do want to keep possibilities for customized overlays created by app partners or providers of utility code/libraries. Your suggestion is too restricting with no benefit.

Even if we wanted developers to create their own "customized overlays" – though I don't understand what that means or why we'd want to allow that (it will only lead to pain on everybody's part) – they could still do so even if the initializer is not there. I think there's a very clear benefit. Developers should not create an instance of the base class, therefore, no initializers should be available.

Regarding 3. message/slider footer

I think I'm following you here and I think I can get on board with that.

Regarding 4. min/max values

I think this was a misunderstanding on my part. I missed the "if there is no message" part. It's a bit concerning to me that there's "hidden" functionality. We know that 99.9% of developers don't read the documentation, so knowing the limits of minimumValue and maximumValue, and the differences for having a message and not having it, are concerning to me.

I'm unsure about this myself, but what do you think about yet another SDLSliderController overlay class? One for having a message and one for having number values?

Regarding 5. value picker message

I missed the title 🙄

Regarding 6. the actual value

The issue I have here is that the overlay class itself has no way of knowing when the value is set by SDL. The only way the caller can know about when the value becomes available is through SDLOverlayControllerCompletionHandler which does not provide the overlay itself or the value in the parameters it passes. Therefore, the caller would look like:

self.slider = [[SDLSliderController alloc] initWithTitle:"Hello" value:4 minimumValue:3 maximumValue:6]

[self presentOverlayController:slider completion:^(SDLResult result, NSError *error) {
  [self doSomethingWith:self.slider.value];
}];

You're right that there's no retain cycle if the code is written like this, but if slider is assigned to a property of the class, then there is (because of reference to self), which is what I was thinking of with the retain cycle. My suggestion is to change the SDLOverlayControllerCompletionHandler to:

typedef void (^SDLOverlayControllerCompletionHandler)(SDLViewController *presentingViewController, SDLOverlayController *overlay, SDLResult result, NSError *error);

in order to avoid that case. Devs will still have to contend with the retain cycle if they reference self themselves, but it would help, e.g. the above case would become:

self.slider = [[SDLSliderController alloc] initWithTitle:"Hello" value:4 minimumValue:3 maximumValue:6]

[self presentOverlayController:slider completion:^(SDLViewController *presentingViewController, SDLOverlayController *overlay, SDLResult result, NSError *error) {
  [(MySDLMusicViewController *)presentingViewController doSomethingWith:(SDLSliderController *)overlay.value];
}];

Additional Questions

  1. What happens if the developer creates SDLValuePickerController or SDLSliderController with invalid values (e.g. > 26 values, the value is outside the min/max range). We can either throw an exception or return nil from the initializer. I'd recommend the exception route.
jordynmackool commented 5 years ago

The Steering Committee voted to return this proposal for revisions on 2018-12-18 to allow the author to add more details as discussed in this comment and this comment.

joeljfischer commented 5 years ago

Hey Kujtim, thanks for the update. Some notes and thoughts:

  1. I think the SDLView opaque property is going to end up being confusing to developers because the opaque property only matters if it's on an overlay controller, right? Am I right to think that e.g. Setting a view to transparent on a standard SDLViewController will have no effect? I can't think of a better way to do it without it being "all or nothing."

In addition, your updates have not addressed my concern (2) above. I'm unsure of what you mean by "customized overlays." I'm assuming you mean developer-created replacements for our overlay classes, which seems like it will only end with pain for us and them (e.g. MOBILE_API changes, incompatibility bugs, them not reporting bugs on the standard framework back to us, etc.).

(4) above seems possibly partly addressed in the "alternatives considered," but my specific concern or proposed solution wasn't addressed, and I'm still fairly concerned about the non-obvious behaviors. What do you think about another overlay class to fix this issue? I don't like splitting it out so many times, but it seems like it's the best way to make the functionality clear.

(6) I'm not in full agreement here, but neither is it the hill I want to die on. Either way works.

Additional Question (1) was also not addressed. I would like to see an exception thrown if an out of bounds size is created for the sliders.

kshala-ford commented 5 years ago

Hello Joel, thank you for the review. Good to have you back 👍

  1. The opaque property is supposed to be used anywhere, not only for overlays.
  2. I still have hope to developer being able to deal with subclasses these days but I'm OK with having the overlay base class init method being private. It would be a potential feature anyway and we can add it later if ever needed.
  3. I understood your point to SDLSliderController as a concern that message overrides the localized value list. The alternative solution was to concat the localized value with the message so that we don't need a third value class. I have another option in mind. The SDLSliderController requires a message string. The SDLValuePickerController should take over the localized value list. See below code change:
@interface SDLSliderController : SDLOverlayController
// This is the only initialiser for this controller. A single message string will be used for the slider footer
- (instancetype)initWithTitle:(nonnull NSString *)title
                      message:(nonnull NSString *)message
                        value:(NSInteger)value
                 minimumValue:(NSInteger)minimumValue
                 maximumValue:(NSInteger)maximumValue;
@end

@interface SDLValuePickerController : SDLOverlayController
// this initialiser automatically creates localized value labels
- (instancetype)initWithTitle:(nonnull NSString *)title
                        value:(NSInteger)value
                 minimumValue:(NSInteger)minimumValue
                 maximumValue:(NSInteger)maximumValue;
// This initialiser allows apps to specify their own value labels
- (instancetype)initWithTitle:(nonnull NSString *)title
                  valueLabels:(nonnull NSArray<NSString *> *)valueLabels
                        value:(NSInteger)value;
@end

Above actually moves the init method from the slider controller to the value picker controller. If I'm not mistaken it solves your concern of the message override and my concern of three classes 👍

  1. So we can agree to not expose the value over the completion handler.

Additional Question 1. I'm not OK with throwing exceptions. I understand that app developers will receive crashlogs when using exceptions but the user will be faced with a bad experience. I guess it's worse to have the app to crash compared to not perform the requested action. The user can continue using other areas of the app if we use error messages. The initialiser should log an error message. I'm not sure if we can throw exceptions in DEBUG but errors in RELEASE? ...

joeljfischer commented 5 years ago
  1. Can you give an example of a non-overlay related use-case?
  2. 👍 It is true that it's easier to expand to add it (minor change) than to remove it if it's causing problems (major change).
  3. I'm good with the addition of an initializer like you describe. That should work well.

Additional question 1: Well, the main reason I advocate for an exception is that an out of bounds scenario shouldn't happen in release if it doesn't happen in development. The other reason is that it's most likely going to be an unhandled and undefined scenario for the developer if creating the overlay fails, and the only real way to get the dev's attention of a failure in production is a crash or a user emailing about something not working. We could also NSAssert, which would happen in debug, but not release by default. I wish we had swift so we could throw and the dev could choose to try! or catch themselves.

NicoleYarroch commented 5 years ago
  1. Is there a limit to the number of view controllers a developer can push on the stack? I can see developers getting very confused with the transparent/opaque terminology if they are stacking multiple view controllers.
  2. If a SDLView in a view controller is transparent, and another view controller overlays it that is transparent or opaque, then nothing in the transparent view should show through, right?
jordynmackool commented 5 years ago

The Steering Committee voted to keep this proposal in review to allow the author to respond to the outstanding questions in this comment and this comment discussing transparent/opaque behavior.

kshala-ford commented 5 years ago

@joeljfischer @NicoleYarroch

  1. The only example I can give is UIView.opaque. Alternatively we could document that, different to normal view controllers, overlay view controller views are transparent.

The other 1. :) There is no limit to the stack. I believe it's the best choice to have views opaque by default and let the developer make them transparent. Then the developers know's what they do.

  1. @NicoleYarroch I'm afraid I don't understand your question
NicoleYarroch commented 5 years ago
  1. @NicoleYarroch I'm afraid I don't understand your question.

I hope this modification of your example can help explain my question regarding overlaying transparent views.

Example

App presents the following overlay controller with view hierarchy:

Will the resulting screen be:

OR

kshala-ford commented 5 years ago

Thanks @NicoleYarroch The resulting screen will be * ("loading", "artist", "album", "genre") because toe overlay's textView is transparent.

jordynmackool commented 5 years ago

The Steering Committee voted to return this proposal for revisions to allow the author to update the proposal. The author is to include the revisions discussed in this comment and expand on his response in the first point in this comment.

theresalech commented 5 years ago

@kshala-ford please advise if you will be able to take action on this proposal within the next two weeks. If not, we will plan to close as inactive on 2019-05-08. Thank you!

theresalech commented 5 years ago

Closing as inactive. The issue will remain in a returned for revisions status and unlocked so the author can notify the Project Maintainer when it is ready to be revisited.