Skip to content

Support all keyboard actions. (#11344)#18855

Merged
matthew-carroll merged 16 commits intoflutter:masterfrom
matthew-carroll:11344_support_all_keyboard_actions
Jul 2, 2018
Merged

Support all keyboard actions. (#11344)#18855
matthew-carroll merged 16 commits intoflutter:masterfrom
matthew-carroll:11344_support_all_keyboard_actions

Conversation

@matthew-carroll
Copy link
Contributor

Support all keyboard actions for iOS and Android. (#11344)

There is a corresponding PR for the engine:
flutter/engine#5620

previous,

/// Complete the text input operation. iOS only, corresponds to "continue."
continue_action,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to be camelcase instead of underscore case (like the emergencyCall member below).


/// iOS only.
emergencyCall,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done, join, route, and emergencyCall need more documentation.

The ones that say "Android only" or "iOS only" should say what they do on the other platforms.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Hixie you mentioned they should "say what they do" - do you mean semantically? Because these buttons don't do anything unless the developer implements something. I think they're all presentation options, perhaps with intended results.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well for example for the others you say what they correspond to on Android.

For all of the values in the enum it'd be good to say what to expect the keyboard to look like on each platform.

'The requested TextInputAction "$inputAction" is not supported on Android.',
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is debug-only behaviour, then the method should be called "_debugEnsure..." (starting with "debug") and the entire body should be in an assert. And it should be called from an assert. Also this should be very clearly documented; my initial guess based on the docs earlier was that unsupported values would just do the same as "unspecified" on unsupported platforms, the same way that the value that only works on recent iOS versions works. (speaking of which, that should also be documented)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Hixie Can you explain the difference between debug and not debug?

And you're saying a method with assertions should be called from an assert? Or you're saying this method should throw exceptions and then the method should be called from an assert?

Do you think the documentation should go at the Widget level or within text_input.dart or both?

Lastly, I think the default that we currently have for an unrecognized action is actually "done", not "unspecified". Do you think the default for a bad input should be "unspecified"? Also, do you think that passing in the wrong platforms action should be a silent failure to "unspecified" instead of doing assertions?

This is what I asked about previously with regard to debug logging vs crashing and I went with the judgement call to crash.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flutter has several modes, of which "debug" and "release" are the prominent ones. In "debug" mode (what you use during development), asserts run. In release mode they do not.

Some methods only do things in debug mode, because in release mode they have no side-effects. The method you wrote here is an example of such a method. We call these debug-only methods. Our style guide says that debug-only identifiers should be prefixed with "debug".

The style guide further says that debug methods should only be called from debug code, i.e. from within an assert.

Do you think the documentation should go at the Widget level or within text_input.dart or both?

Probably both. They're different layers -- someone could write their own framework on top of text_input.dart, so we need to document what it does at that level. But similarly, most people will use the widget layer, so the widgets should document what they do too.

Lastly, I think the default that we currently have for an unrecognized action is actually "done", not "unspecified". Do you think the default for a bad input should be "unspecified"?

I don't have a strong opinion. I can see the logic in using the platform default, since it's presumably the default for a reason. But also "done" seems to make sense as a default. So... your call.

Also, do you think that passing in the wrong platforms action should be a silent failure to "unspecified" instead of doing assertions?

I don't have a strong opinion. In release builds the assertions don't do anything, so by using assertions we're saying there's one behaviour in release builds and one in debug builds. So we should think about what we want to have happen in release builds regardless. We should also consider how much we want to support people developing on Android from iOS and vice versa. The gallery has a toggle to switch platforms; how would this work with this feature? Do we expect people to use the platform-specific options? Do we expect them to only use them on platform-specific apps? All these questions are some we should consider here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed the debug method and moved it into an assert. I added docs to both the TextInputConfiguration and EditableText that explain platform differences for input types. I also updated the engine behavior so that on both iOS and Android if an unrecognized input type value is sent, the OS will go with "default" or "unspecified" depending on which platform you're talking about.

/// behaviors based on the situation:
///
/// - When a completion action is pressed, such as "done," "go," "send," or
/// "search," the user's content is submitted to the [controller] and then
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"search",, with the comma outside the quotes, since the action isn't called search, with a comma.

/// "search," the user's content is submitted to the [controller] and then
/// focus is given up.
///
/// - When a non-completion action is pressed, such as "next" or "previous,"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

/// Called when the text being edited changes.
final ValueChanged<String> onChanged;

/// Called when the user submits editable content (ex: user presses the "done"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/ex/e.g./

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you elaborate on what you're requesting here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the syntax s/pattern/replacement/ is used in various unix programs (sed, vim, perl, etc) to mean "replace the 'pattern' part with 'replacement'". So in this case, I meant "replace the "ex" (in the parenthetical) with "e.g."".

@@ -280,9 +281,30 @@ class EditableText extends StatefulWidget {
/// The type of keyboard to use for editing the text.
final TextInputType keyboardType;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

somewhere (maybe the class documentation) we should have a section that talks about the event lifecycle (e.g. that onEditingComplete happens before onSubmitted but after onChanged, or whatever)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a few lines to the class docs. Let me know if you have any comments.

/// another input widget within [onSubmitted].
///
/// Providing [onEditingComplete] prevents the aforementioned default behavior.
final VoidCallback onEditingComplete;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems to be missing the constructor argument

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@Hixie
Copy link
Contributor

Hixie commented Jun 26, 2018

tests?

@matthew-carroll
Copy link
Contributor Author

@Hixie Yes, tests are needed. Do we have any good examples of writing effective tests across the framework/engine boundary?

Matt Carroll added 2 commits June 26, 2018 17:43
@Hixie
Copy link
Contributor

Hixie commented Jun 27, 2018

The main thing I think needs testing here is the event handling in the text field, which you should be able to do entirely on the framework side.

For the channels, there's three kinds of tests you can write that I can think of:

  1. integration tests using devicelab (tests that communication across framework and engine works, and that it works on devices)
  2. framework-side tests, with a mocked engine (unit test in flutter repo)
  3. engine-side tests, with a mocked framework (unit test in engine repo)

@matthew-carroll
Copy link
Contributor Author

@Hixie I've added tests for this PR and also a test for the fix to receiveAction(). If things look good to you then I'll merge in, along with the engine PR.

);
}
return true;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should wrap all of this in an assert to make it clear that this does nothing in release builds

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The invocation is already wrapped in an assert. You want a 3rd one? An assert calling an assert comprised of 2 asserts?

/// receives focus. For example, providing iOS's "emergencyCall" action when
/// running on an Android device will result in an error. Appropriate
/// [inputAction]s can be chosen by checking the current platform and then
/// selecting the appropriate action.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the error's only thrown in debug mode, right? the docs are unclear about this.


// Tests that the desired keyboard action button is requested.
//
// I.E., when an EditableText is given a particular [action], Flutter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i.e. is lowercase. To avoid starting with a lowercase letter consider "In other words" or some other phrase. (I'm not sure "i.e." or "in other words" really works here though?)

textInputAction: action,
style: textStyle,
cursorColor: cursorColor,
))));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not flutter style

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That actually isn't new code, but I'll fix it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact there's a lot of it so I'll correct it everywhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somehow my comments here disappeared. This isn't actually new code, but this format issue is in this file a number of times so I'll correct them all.

'TextInputClient.performAction',
<dynamic>[_client, action.toString()],
Future<Null> receiveAction(TextInputAction action) async {
return TestAsyncUtils.guard(() async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is the inner one async? i don't see any awaits and you return a Future, so you're not relying on the async wrapping behaviour.


// No error was found. Complete without issue.
completer.complete();
} catch (e) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style guide says to avoid abbreviations; we usually call this error or exception.

/// The action to take when the enter button is pressed in a multi-line
/// text field (which is typically to do nothing).
newline,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the docs for this enum still seem like they could be more detailed, for example they could say which will cause the focus to be lost and which will not, those that assert on unsupported platforms could mention the assertion you get in debug mode and what happens in release mode, and it's not entirely clear what it means for one of these to "correspond" to something (e.g. what does it mean for "emergencyCall" to correspond to "Emergency Call"?).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think documenting behavior here makes sense. No selection of a given value implies a reliable behavior. The definition of the behavior that we choose to use by default is found in 'performAction()' within editable_text.dart. But even those behaviors can be easily overridden to do what you want.

The fact that the button says "Emergency Call," for example, says nothing about what it does. Tapping that button on iOS does nothing unless you tell it to.

I'm also concerned about replicating debug vs released mode error reporting here. This would be the 3rd place we're stating that same thing, and I'm concerned that if something changes, we're making it very easy to forget to update one of those docs...

So I'm really not sure how much information we want here. For the most part these enums are simply direct replications of other enums on the platform so that developers can select what they want (supposedly based on their existing knowledge of those platforms). And to be clear, the only situation out of all of these enums where we are generalizing terms is the "unspecified" term. We send that term directly to Android, but for iOS we translate it to "default". But that's the only genericizing we do when compared to the actual platform names for the same thing.

/// For example: If the user presses the keyboard action button on iOS when it
/// reads "Emergency Call," the result should not be a focus change to the next
/// TextField. This behavior is not logically appropriate for a button that says
/// "Emergency Call."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we use the "programming" style of quotation in the docs, so the commas and periods should be after the quote marks.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this also applies in other parts of these docs)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah...gotta break this habit.

/// thrown. If the same thing is done in release mode, then instead of sending
/// the inappropriate value, Android will use "unspecified" on the platform
/// side and iOS will use "default" on the platform side.
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these docs are fantastic. thanks!

/// iOS: Corresponds to iOS's "UIReturnKeyContinue". The title displayed in the
/// action button is "Continue". This action is only available on iOS 9.0+.
///
/// Note: the reason that this value has "Action" post-fixed to it is because
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drop the "Note:", it's cleaner. :-)

/// iOS: Corresponds to iOS's "UIReturnKeyDefault". The title displayed in the
/// action button is "return".
///
/// Notice that [TextInputAction.newline] is a term that exists in Flutter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than "notice that .. is a term that exists ..", consider just "The term .. exists .."

assert(
_androidSupportedInputActions.contains(inputAction),
'The requested TextInputAction "$inputAction" is not supported on Android.',
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the insides of the inner asserts are now underindented

@Hixie
Copy link
Contributor

Hixie commented Jun 29, 2018

LGTM modulo minor nits

@matthew-carroll matthew-carroll merged commit 2a65505 into flutter:master Jul 2, 2018
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 10, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants