Detect when delete is typed into a TextField

I'm attempting to make a "CodeEntry" widget, consisting of 4 separate TextFields that accept a single digit each:

It should auto focus the next fields as it's being filled out, and move to the previous ones if being deleted.
The problem occurs when a new field is focused and 'Delete' is immediately typed. It should move back to previous field. It will not work since the onChanged is not called.
Any ideas?

While Flutter doesn't (yet) support a key event API, one thought that might help: you could do this as a widget that uses a single text field with no decorations that's overlaid on top of the four boxes. This give you support for pasting as well, though you'd probably want a means to disable selection, which would look pretty ugly.

I encountered the same problem

cc @jslavitz @gspencergoog for any thoughts/tracking since we've previously discussed focus navigation.

i have same problem... anyone have idea?

Attach a TextEditingController (_controller) to each of the TextFields/TextFormFields.
add a onTextChanged() Listener to the controller.
in onTextChanged() save the value of the controller to an instance field (_myVars), and do a simple comparison.

DISCLAIMER: For the dartfmt Die Hards...I intentionally do not conform to dartfmt's fugly code style conventions. You have been warned. ;)
here's the gist of it....
// number of "boxes"
static const NUM_BOXES = 8;
final _boxControllers = List<TextEditController>();
final _boxFocusNodes = List<FocusNode>();
List<String> _myVars = List<String>(NUM_BOXES);

@override
void initState()
{
   super.initState();
   for (var i = 0; i < NUM_BOXES; i++)
   {
      if (_myVars[i] == null)
         _myVars[i] = "";

      final controller = TextEditingController();
      controller.addListener(() => _onTextChanged(i));
      _controllers.add(controller);
      _focusNodes.add(FocusNode());
   }
}

void _onTextChanged(int index)
{
   String value = _controller[index].text ?? ""
   if (_myVars == value) return;
   _myVars = value;
   if (value.isEmpty)
   {
       // request focus for the previous "box"
       FocusScope.of(context).requestFocus(PREVIOUS_FOCUS_NODE);
       return;
   }
   // request focus for the next "box"
   FocusScope.of(context).requestFocus(NEXT_FOCUS_NODE);
}

@cbracken @wooldridgetm You can detect if a delete key is typed. If you go to editable.dart and look at _handleKeyEvent(), you can see that the delete key press is handled in there.
If you want to see if the delete key is pressed, you just need to add a RawKeyboardListener and then check to see if the pressed keycode is the keycode for delete. Which, on Android, is 112.

Attach a TextEditingController (_controller) to each of the TextFields/TextFormFields.
add a onTextChanged() Listener to the controller.
in onTextChanged() save the value of the controller to an instance field (_myVars), do a comparison, and requestFocus for the previous or next "box".

DISCLAIMER: For the dartfmt Die Hards...I intentionally do not conform to dartfmt's fugly code style conventions. You have been warned. ;)
here's the gist of it....
// number of "boxes"
static const NUM_BOXES = 8;
final _boxControllers = List<TextEditController>();
final _boxFocusNodes = List<FocusNode>();
List<String> _myVars = List<String>(NUM_BOXES);

@override
void initState()
{
   super.initState();
   for (var i = 0; i < NUM_BOXES; i++)
   {
      if (_myVars[i] == null)
         _myVars[i] = "";

      final controller = TextEditingController();
      controller.addListener(() => _onTextChanged(i));
      _boxControllers.add(controller);
      _boxFocusNodes.add(FocusNode());
   }
}

void _onTextChanged(int index)
{
   String value = _controller[index].text ?? ""
   if (_myVars[index] == value) return;
   _myVars[index] = value;
   if (value.isEmpty)
   {
       // request focus for the previous "box"
       FocusScope.of(context).requestFocus(PREVIOUS_FOCUS_NODE);
       return;
   }
   // request focus for the next "box"
   FocusScope.of(context).requestFocus(NEXT_FOCUS_NODE);
}

This partially solves the issue...when the textfield is empty and you press backspace it is expected to go to the previous textfield if it you are not in the first textfield...your logic does not handle this...thanks anyways.

No, my logic doesn't handle that. I don't know how to detect when backspace or delete key is pressed and the text isn't changed.  If I could, then I wouldn't need the bandaid above.
...but I would argue that it solves the more important use-case, and some functionality is better than none ;)

Here's my solution https://github.com/LiewJunTung/pin_code_text_field/blob/master/lib/pin_code_text_field.dart
I am using a single hidden TextField which is 1 px width and 1px height, created the boxes with Containers and use FocusNode and GestureDetector to make this pin widget. The downside is that it doesn't support copy and paste. 😅

📌

are there any logically correct solutions for that issue?

Can you not just save the last word in the onChanged of the form or textfield in a variable and then compare it to the next value of the textfield on the next onChanged? If the next value is shorter than it was backspace.

Here's my solution.
I'm pretty new to dart and flutter so it would be nice to hear from you if I could improve the code in at any point. My solution is in no way good practice, but better than none.

It's working, if you are using a TextField for every code-digit, e.g. 4 digit code -> 4 textfield´s

The idea is to use a TextField with maxLength: 2, which initially holds a hidden (TextStyle(color: !userValue ? Colors.transparent : Colors.black) character, which can be deleted and calls the onChanged Event.
TextField(
            controller: _controller,
            focusNode: widget.focusNode,
            onChanged: (value) {
              if (value.startsWith('.')) {
                _controller.text = _controller.text[1];
                setState(() {
                  userValue = true;
                });

                widget.focusNode.unfocus();
                widget.inputDone(widget.index, _controller.text);
                //parent widget which calls the next focusNode to focus
              } else if (value.isEmpty) {
                widget.inputDeleted(widget.index);
                setState(() {
                  userValue = false;
                });
              }
            },
            enabled: false,
            keyboardType: TextInputType.number,
            maxLength: 2,
            style: TextStyle(
                color: !userValue ? Colors.transparent : Colors.black),
            decoration: null,
          ),

The enabled attribute helps to prevent onTap events. When the user enters a Character value.startsWith('.') is true and the placeholder character will be replaced by the entered digit. At the same time   setState(() {userValue = true;}); calls the build action with a black text color, so the user can see the digit.
In the build function set the initial value every time the widget is rebuild and there is currently no digit.
    if (!userValue) {       _controller.text = '.';     }
Finally add a  listener to the focusNode:
`    widget.focusNode.addListener(() {
if (widget.focusNode.hasFocus) {
if (userValue) {
setState(() {
userValue = false;
});
      _controller.text = '.';
    }
  }
});`

There happens the magic: If the node gets again focus (user input already done -> userValue == true)
the user pressed the delete button in the input with index + 1 (-> deleted the '.' character (else if (value.isEmpty) {                 widget.inputDeleted(widget.index);                 setState(() {                   userValue = false;                 } got called)) and we want to delete the digit in the current input and replace it by the initial value.
widget.inputDeleted(widget.index);
widget.inputDone(widget.index, _controller.text);

handles the focus selection of the following or previous FocusNode / TextField.
Hope this helps.

@oli06 a good way, but it looks a bit stranger because of the movement of the cursor...

I have the same problem and I think you can  solve it by creating your own key borad

I have the same problem and I think you can  solve it by creating your own keyboard

RawKeyboard.instance.addListener can't read Android (Pixel 3a and Zenfone) enter key tapped.

@OOMROO @gofur
Did you sucess?

it use just onChanged. it is not good.

@aimsugita, the RawKeyboard class only provides events from actual hardware keyboards, not soft keyboards. There's currently no way to receive raw key events from soft keyboards, since many of them don't provide this event information.

did any one find solve ?

did any one find solve ?

https://pub.dev/packages/pin_code_text_field i use this package..

The problem with pin_code_text_field is that it won't let you paste the code in.

The Problem really is that onChanged doesnt trigger when there is no character inside..

Here's my solution.
I'm pretty new to dart and flutter so it would be nice to hear from you if I could improve the code in at any point. My solution is in no way good practice, but better than none.

It's working, if you are using a TextField for every code-digit, e.g. 4 digit code -> 4 textfield´s

The idea is to use a TextField with maxLength: 2, which initially holds a hidden (TextStyle(color: !userValue ? Colors.transparent : Colors.black) character, which can be deleted and calls the onChanged Event.
TextField(
            controller: _controller,
            focusNode: widget.focusNode,
            onChanged: (value) {
              if (value.startsWith('.')) {
                _controller.text = _controller.text[1];
                setState(() {
                  userValue = true;
                });

                widget.focusNode.unfocus();
                widget.inputDone(widget.index, _controller.text);
                //parent widget which calls the next focusNode to focus
              } else if (value.isEmpty) {
                widget.inputDeleted(widget.index);
                setState(() {
                  userValue = false;
                });
              }
            },
            enabled: false,
            keyboardType: TextInputType.number,
            maxLength: 2,
            style: TextStyle(
                color: !userValue ? Colors.transparent : Colors.black),
            decoration: null,
          ),

The enabled attribute helps to prevent onTap events. When the user enters a Character value.startsWith('.') is true and the placeholder character will be replaced by the entered digit. At the same time  setState(() {userValue = true;}); calls the build action with a black text color, so the user can see the digit.
In the build function set the initial value every time the widget is rebuild and there is currently no digit.
 if (!userValue) { _controller.text = '.'; }
Finally add a listener to the focusNode:
widget.focusNode.addListener(() {
   if (widget.focusNode.hasFocus) {
     if (userValue) {
       setState(() {
         userValue = false;
       });

       _controller.text = '.';
     }
   }
 });

There happens the magic: If the node gets again focus (user input already done -> userValue == true)
the user pressed the delete button in the input with index + 1 (-> deleted the '.' character (else if (value.isEmpty) { widget.inputDeleted(widget.index); setState(() { userValue = false; } got called)) and we want to delete the digit in the current input and replace it by the initial value.
widget.inputDeleted(widget.index);
widget.inputDone(widget.index, _controller.text);

handles the focus selection of the following or previous FocusNode / TextField.
Hope this helps.
I uploaded a gif to show the solution: https://media.giphy.com/media/H3GHO27xBm5aVp8oZA/giphy.gif

I just implemented this idea and it worked, thanks!

I found two acceptable solutions:

RawKeyboardListener Widget that provides events from virtual keyboard when is focused.

TextInputClient interface for TextInput.attach(client, config) this is actually way how standard TextField works.

Both methods are not so hard to implement and you end up with fully customizable and controllable 'Code Field'.
RawKeyboardListener gives you more freedom, but is harder to implement, because you need to handle all inputs and keyboard handling by your self.
TextInput/TextInputClient do a lot of work for you..
Here is my approach: https://github.com/romanbase/code_field
I used TextInputClient and everything works well. I don't have time to implement Clipboard toolbar so I ended up with GestureDetector and simply pasting date from Clipboard onLongTap with input validator.
Widget is easily customizable and Logic part provides same validation etc. Just check example project.

Beware of using RawKeyboardListener in this way: it actually is only designed to respond to hardware key events. This is probably working for you because these events are simulated by some soft keyboards, but many soft keyboards do not simulate hardware key events.

  TextEditingController second = TextEditingController();
  TextEditingController third = TextEditingController();
  TextEditingController fourth = TextEditingController();

  FocusNode firstNode = FocusNode();
  FocusNode secondNode = FocusNode();
  FocusNode thirdNode = FocusNode();
  FocusNode fourthNode = FocusNode();
  @override
  void initState() {
    super.initState();

    first.addListener(() {
      setState(() {});
      if (first.text.length > 0) {
        secondNode.requestFocus();
      }
    });
    second.addListener(() {
      setState(() {});
      if (second.text.length > 0) {
        thirdNode.requestFocus();
      }
    });
    third.addListener(() {
      setState(() {});
      if (third.text.length > 0) {
        fourthNode.requestFocus();
      }
    });
    fourth.addListener(() {
      setState(() {});
      if (fourth.text.length > 0) {
        fourthNode.unfocus();
      }
    });
  }

  @override
  void dispose() {
    first.dispose();
    second.dispose();
    third.dispose();
    fourth.dispose();

    firstNode.dispose();
    secondNode.dispose();
    thirdNode.dispose();
    fourthNode.dispose();
    super.dispose();
  }

Try the above and in your build method add four textFields and assign each appropiate controllers and focusnodes

@Iampato : The question is not about moving to the next textEditingController, the question is about going back, when you tap Backspace on the keypad.

TextEditingController first = TextEditingController();
  TextEditingController second = TextEditingController();
  TextEditingController third = TextEditingController();
  TextEditingController fourth = TextEditingController();

  FocusNode firstNode = FocusNode();
  FocusNode secondNode = FocusNode();
  FocusNode thirdNode = FocusNode();
  FocusNode fourthNode = FocusNode();
  @override
  void initState() {
    super.initState();

    first.addListener(() {
      setState(() {});
      if (first.text.length > 0) {
        secondNode.requestFocus();
      }
    });
    second.addListener(() {
      setState(() {});
      if (second.text.length > 0) {
        thirdNode.requestFocus();
      }
    });
    third.addListener(() {
      setState(() {});
      if (third.text.length > 0) {
        fourthNode.requestFocus();
      }
    });
    fourth.addListener(() {
      setState(() {});
      if (fourth.text.length > 0) {
        fourthNode.unfocus();
      }
    });
  }

  @override
  void dispose() {
    first.dispose();
    second.dispose();
    third.dispose();
    fourth.dispose();

    firstNode.dispose();
    secondNode.dispose();
    thirdNode.dispose();
    fourthNode.dispose();
    super.dispose();
  }

Try the above and in your build method add four textFields and assign each appropiate controllers and focusnodes

@Iampato : The question is not about moving to the next textEditingController, the question is about going to previous textEditingController, when you tap Backspace on the keypad.

@ oli06 un bon moyen, mais ça a l'air un peu plus étrange à cause du mouvement du curseur ...

I use a TextField for each code, with 6 textField and the erase function wonderfully when I fill all the times but the disadvantage is when I am at a position of 6 I can no longer delete
TextField(
controller: _controller,
focusNode: widget.focusNode,
onChanged: (value) {
if (value.startsWith('.')) {
_controller.text = _controller.text[1];
setState(() {
userValue = true;
});
            widget.focusNode.unfocus();
            widget.inputDone(widget.index, _controller.text);
            //parent widget which calls the next focusNode to focus
          } else if (value.isEmpty) {
            widget.inputDeleted(widget.index);
            setState(() {
              userValue = false;
            });
          }
        },
        enabled: false,
        keyboardType: TextInputType.number,
        maxLength: 2,
        style: TextStyle(
            color: !userValue ? Colors.transparent : Colors.black),
        decoration: null,
      ),

if I could know if the user pressed backspace I could maybe settle the problem
thanks in advance for your help

Hi @pauldemarco,
Can you please confirm if this is still an issue on latest Flutter version (eg: dev, master) ?
Thanks.

Persists with current master 1.21.0-6.0.pre.41.

code sample
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Retrieve Text Input',
      home: MyCustomForm(),
    );
  }
}

class MyCustomForm extends StatefulWidget {
  @override
  _MyCustomFormState createState() => _MyCustomFormState();
}

class _MyCustomFormState extends State<MyCustomForm> {
  final myController = TextEditingController();

  @override
  void initState() {
    super.initState();

    myController.addListener(_printLatestValue);
  }

  @override
  void dispose() {
    myController.dispose();
    super.dispose();
  }

  _printLatestValue() {
    print("Second text field: ${myController.text}");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Retrieve Text Input'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: <Widget>[
            TextField(
              onChanged: (text) {
                print("First text field: $text");
              },
            ),
            TextField(
              controller: myController,
            ),
          ],
        ),
      ),
    );
  }
}

flutter doctor -v
[✓] Flutter (Channel master, 1.21.0-6.0.pre.41, on Mac OS X 10.15.6 19G73, locale en-GB)
    • Flutter version 1.21.0-6.0.pre.41 at /Users/nevercode/dev/flutter
    • Framework revision 39fa00201d (3 hours ago), 2020-07-27 21:11:43 -0700
    • Engine revision f27729e97b
    • Dart version 2.10.0 (build 2.10.0-0.0.dev 24c7666def)
    • Pub download mirror https://pub.flutter-io.cn
    • Flutter download mirror https://storage.flutter-io.cn

[✓] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
    • Android SDK at /Users/nevercode/Library/Android/sdk
    • Platform android-29, build-tools 29.0.3
    • Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 11.6)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Xcode 11.6, Build version 11E708
    • CocoaPods version 1.9.1

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 4.0)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin version 47.1.2
    • Dart plugin version 193.7361
    • Java version OpenJDK Runtime Environment (build 1.8.0_242-release-1644-b3-6222593)

[✓] VS Code (version 1.47.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.12.2

[✓] Connected device (5 available)
    • Redmi Note 7 (mobile)       • 6345c469                                 • android-arm64  • Android 9 (API 28)
    • Nevercode’s iPhone (mobile) • b668e524315069f3db3661ac11ff1f66afafebdb • ios            • iOS 13.6
    • macOS (desktop)             • macos                                    • darwin-x64     • Mac OS X 10.15.6 19G73
    • Web Server (web)            • web-server                               • web-javascript • Flutter Tools
    • Chrome (web)                • chrome                                   • web-javascript • Google Chrome 84.0.4147.105

• No issues found!

I think it is well the time that Flutter should spec its key event mechanics. RawKeyboardListener that only works on MacOS and Web is far from the solution. Basically we can't get anything more than the default textfield on mobile without tricks.

The problem with suggested diff-based key detection is that they can never support copy/paste and undo/redo at the same time.
Copy/paste can be supported by resetting TextEditingValue every time to maintain a correct selection. (If you don't reset everytime, then the cursor can move into those tagging characters can copy nonsense from them.)
Undo/Redo can be supported by appending a random tag to the TextEditingValue and save the history to a map.
But when combined, copy/paste's resetting will break undo/redo's history.  So the only solution is to forward those key event in the flutter engine as suggest in https://github.com/flutter/flutter/issues/29845

@pedromassango see https://github.com/flutter/flutter/issues/14809#issuecomment-565111460 and https://github.com/flutter/flutter/issues/14809#issuecomment-625888639 : the underlying OS APIs don't provide key events from the soft keyboard in the first place, so it's probably very hard to fix in any way that makes sense.

@pedromassango see #14809 (comment) and #14809 (comment) : the underlying OS APIs don't provide key events from the soft keyboard in the first place, so it's probably very hard to fix in any way that makes sense.

Do you have any suggestion in how we can fix this? I can only think of detect the deletion of a character but maybe this will not work for other use cases.

For software keyboards, no (if I did, we would have fixed this already). They just don't provide the events: they're Input Method Editors, essentially, and if you think about it, they're separate applications that take touch input and produce editing event streams to send via a text editing connection to the app, they don't actually activate any buttons on the device. The fact that some of them simulate hardware key events is up to the implementer of the keyboard, and Android doesn't enforce that they do that.
To be fair, if you were a software keyboard implementer, it's hard to do in a good way: imagine trying to figure out what key codes and scan codes to use when sending an emoji created by the "emoji kitchen" on GBoard? It's more than just the Unicode sequence needed to generate the text. Of course, a 95% solution where they punt on Emoji and other non-keyboard characters would still be pretty helpful here, to be sure.

For 'code field' or any other custom Input Widget is possible to implement TextInputClient  and TextInputConnection to detect String input and other Actions from device. With this solution is possible to build any type of Input Widget.

For software keyboards, no (if I did, we would have fixed this already). They just don't provide the events: they're Input Method Editors, essentially, and if you think about it, they're separate applications that take touch input and produce editing event streams to send via a text editing connection to the app, they don't actually activate any buttons on the device. The fact that some of them simulate hardware key events is up to the implementer of the keyboard, and Android doesn't enforce that they do that.
To be fair, if you were a software keyboard implementer, it's hard to do in a good way: imagine trying to figure out what key codes and scan codes to use when sending an emoji created by the "emoji kitchen" on GBoard? It's more than just the Unicode sequence needed to generate the text. Of course, a 95% solution where they punt on Emoji and other non-keyboard characters would still be pretty helpful here, to be sure.

Understood. So it will take a while to have this fixed.

For 'code field' or any other custom Input Widget is possible to implement TextInputClient  and TextInputConnection to detect String input and other Actions from device. With this solution is possible to build any type of Input Widget.

@RomanBase The problem with that is that for this case, the keyboard doesn't even produce an editing event: there's no character to be deleted, so pressing the delete key doesn't edit anything.

For software keyboards, no (if I did, we would have fixed this already). They just don't provide the events: they're Input Method Editors, essentially, and if you think about it, they're separate applications that take touch input and produce editing event streams to send via a text editing connection to the app, they don't actually activate any buttons on the device. The fact that some of them simulate hardware key events is up to the implementer of the keyboard, and Android doesn't enforce that they do that.
To be fair, if you were a software keyboard implementer, it's hard to do in a good way: imagine trying to figure out what key codes and scan codes to use when sending an emoji created by the "emoji kitchen" on GBoard? It's more than just the Unicode sequence needed to generate the text. Of course, a 95% solution where they punt on Emoji and other non-keyboard characters would still be pretty helpful here, to be sure.

I've been tracking your PR https://github.com/flutter/engine/pull/19024 for some time. In theory, if you are overriding onKey on Android, then when this change lands, we should be able to detect soft keyboard that sends onKey event through Focus or other widget, right? (Currently Focus can't detect those event, even if onKey do triggers. I'm not sure if it's by design or a bug)

@gspencergoog Ye, but with TextInputClient you will build just one Input Widget and widget will receive value via TextEditingValue where whole String is stored. So you don't need 4 separated TextFields but just one custom Widget that handles input..

@znjameswu, The event synthesis stuff will help with keeping keys from being passed to other native controls in the same app (when Flutter is added to an existing app) when they are also handled by Flutter, but it probably won't affect whether onKey gets events from soft keyboards: the soft keyboards either synthesize hardware key events when users touch keys, or they don't (and many don't). The event synthesis in my PR is separate and unrelated to the software keyboards.
If they do synthesize those hardware events, they should already show up in onKey handlers. Please file another issue with a reproduction case if you have a situation where that isn't the case.

@znjameswu, The event synthesis stuff will help with keeping keys from being passed to other native controls in the same app (when Flutter is added to an existing app) when they are also handled by Flutter, but it probably won't affect whether onKey gets events from soft keyboards: the soft keyboards either synthesize hardware key events when users touch keys, or they don't (and many don't). The event synthesis in my PR is separate and unrelated to the software keyboards.
If they do synthesize those hardware events, they should already show up in onKey handlers. Please file another issue with a reproduction case if you have a situation where that isn't the case.

Sorry, my bad. I read the design doc again and found I mistook Activity.onKeyDown for View.onKeyListener.

I'm attempting to make a "CodeEntry" widget, consisting of 4 separate TextFields that accept a single digit each:

It should auto focus the next fields as it's being filled out, and move to the previous ones if being deleted.
The problem occurs when a new field is focused and 'Delete' is immediately typed. It should move back to previous field. It will not work since the onChanged is not called.
Any ideas?

so what happens , problem solved or not?

Has anyone found a solution?

Me too, has anyone found a solution?

Me too, has anyone found a solution?

I found a simply solution, use zero-width-space '\uFEFF' replace empty.

Me too, has anyone found a solution?

I found a simply solution, use zero-width-space '\uFEFF' replace empty.

`import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class InputEnglish extends StatefulWidget
{
String word;
InputEnglish({@required this.word});

@override
State<StatefulWidget> createState() => _InputEnglishState(word);

}
class _InputEnglishState extends State
{
String word;
final space = '\uFEFF';

_InputEnglishState(this.word);

var focusList = List<FocusNode>();

var controllerList = List<TextEditingController>();

@override
void initState()
{
    super.initState();

    focusList = List<FocusNode>.generate(word.length, (index){
      return FocusNode();
    });

    controllerList = List<TextEditingController>.generate(word.length, (index){
      return TextEditingController()..text = space;
    });

}

@override
void dispose() {

    focusList.forEach((focus){
      focus.dispose();
    });

    controllerList.forEach((controler){
      controler.dispose();
    });

    super.dispose();
}

@override
Widget build(BuildContext context)
{
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent>{
        // Pressing space in the field will now move to the next field.
        LogicalKeySet(LogicalKeyboardKey.space): const NextFocusIntent(),
      },
      child: FocusTraversalGroup(
        child: Wrap(
            children: List<Widget>.generate(word.length, (index){
              return Padding(
                padding: const EdgeInsets.all(8.0),
                child: ConstrainedBox(
                   constraints: BoxConstraints.tight(const Size(15 , 50)),
                   child: TextField(
                      controller: controllerList[index],
                      autofocus: index == 0 ? true : false,
                      focusNode: focusList[index],
                      textAlign: TextAlign.center ,
                      decoration: InputDecoration(
                        focusColor: Colors.red,
                        fillColor: Colors.green,
                        hoverColor: Colors.red,
                      ),
                      onChanged: (str){
                        print(str.length);

                        if(str.length <= 0){

                          print(index);

                          controllerList[index].value = TextEditingValue(
                            text: space,
                            selection: TextSelection.collapsed(offset: str.length)
                          );

                        }

                        if(str.length <=0 && index != 0){
                          focusList[index - 1].requestFocus();
                          return;
                        }

                        if(str != space && index < word.length - 1 && str.length > 0){
                          focusList[index + 1].requestFocus();
                          return;
                        }

                      },
                   ),
                ),
              );
            }),
          )
      ),
    );

}

}`
this is my solution

`
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class InputEnglish extends StatefulWidget
{
String word;
InputEnglish({@required this.word});

@override
State<StatefulWidget> createState() => _InputEnglishState(word);

}
class _InputEnglishState extends State
{
String word;
final space = "\uFEFF";

_InputEnglishState(this.word);

var focusList = List<FocusNode>();

var controllerList = List<TextEditingController>();

@override
void initState()
{
    super.initState();

    focusList = List<FocusNode>.generate(word.length, (index){
      return FocusNode();
    });

    controllerList = List<TextEditingController>.generate(word.length, (index){
      return TextEditingController()..text = space;
    });

}

@override
void dispose() {

    focusList.forEach((focus){
      focus.dispose();
    });

    controllerList.forEach((controler){
      controler.dispose();
    });

    super.dispose();
}

@override
Widget build(BuildContext context)
{
    return Shortcuts(
      shortcuts: <LogicalKeySet, Intent>{
        // Pressing space in the field will now move to the next field.
        LogicalKeySet(LogicalKeyboardKey.space): const NextFocusIntent(),
      },
      child: FocusTraversalGroup(
        child: Wrap(
            children: List<Widget>.generate(word.length, (index){
              return Padding(
                padding: const EdgeInsets.all(8.0),
                child: ConstrainedBox(
                   constraints: BoxConstraints.tight(const Size(15 , 50)),
                   child: TextField(
                      controller: controllerList[index],
                      autofocus: index == 0 ? true : false,
                      focusNode: focusList[index],
                      textAlign: TextAlign.center ,
                      decoration: InputDecoration(
                        focusColor: Colors.red,
                        fillColor: Colors.green,
                        hoverColor: Colors.red,
                      ),
                      onChanged: (str){
                        print(str.length);

                        if(str.length <= 0){

                          print(index);

                          controllerList[index].value = TextEditingValue(
                            text: space,
                            selection: TextSelection.collapsed(offset: str.length)
                          );

                        }

                        if(str.length <=0 && index != 0){
                          focusList[index - 1].requestFocus();
                          return;
                        }

                        if(str != space && index < word.length - 1 && str.length > 0){
                          focusList[index + 1].requestFocus();
                          return;
                        }

                      },
                   ),
                ),
              );
            }),
          )
      ),
    );

}

}
`

@gspencergoog GMail has a feature where emails are tokenized while they are entered. After having an email tokenized, if you press "delete" in the empty email, the previous tokenized email is deleted.
Do you know how one can accomplish this in Flutter?
After reading through this thread, there are a lot of comments about the platform simply not making this information available, but it looks like GMail on iOS and Android are capable of responding to this behavior. Any thoughts?

Hi Matt, I'm not sure what you mean by "tokenized".
I am not aware of any case where any key events or event IME editing events are delivered when the text field is empty and the backspace on a soft keyboard is pressed. If you can find code that does this, we can figure out how to deliver those events to Flutter. It will already work from a hardware keyboard, since that delivers events.

Note that it also depends on which soft keyboard you use. For instance, the "Hacker's Keyboard" will synthesize hardware key events, so it would work with that keyboard. The default GBoard keyboard does not.

@gspencergoog this is the interaction in GMail that I'm referring to:

@gspencergoog do you have any thoughts about that gmail example?

@slightfoot made a something similar few months ago without knowing that "delete" was pressed.
Code is quite old now and may need some migration/update.
See the gist: https://gist.github.com/slightfoot/c6c0f1f1baca326a389a9aec47886ad6.

@matthew-carroll I agree that they are implementing it, I just don't know the details of how. It could be that they have inserted sentinel characters into the text, as suggested above, and are externally handling the removal of the chip. They also seem to be rendering their own large blinking cursor, so perhaps the chip itself is a specially rendered character? It is clearly a very custom control.
In any case, it should be possible to work around the lack of events from the keyboard for backspace buttons specifically, with special code to handle invisible sentinel characters so that they don't show up in the output, but that in the general case we're still not going get events from the keyboard, because the aren't sent.

I was able to achieve the email use-case with a zero width space. I'm gonna do a video that uses it. I'll try to remember to link it here when I publish it.

I published a library that solves it with a zero width space
https://github.com/federicodesia/code_fields

hi any update on this issue?

Will this answer be somewhat helpful? According to them, overriding InputConnection.sendKeyEvent and InputConnection.deleteSurroundingText should be enough to handle delete on Android.

Hi everyone,  I have for the past day or so I have been working on a workaround to this issue and I am glad to say I found a solution that works for me. I hope my code helps someone.
`
static const int  TOTAL_TEXT_BOX = 5;
int nodeCount;
int nodePosition;
List textController = [];
List focusNode = [];
final ValueNotifier<List> _textBox = ValueNotifier<List>([]);
@override
void initState() {
super.initState();
for(int x = 0; x < TOTAL_TEXT_BOX; x++){
  textController.add(new TextEditingController());
  focusNode.add(FocusNode());
  if(x == 0){
    _textBox.value.add(true);
  }else{
    _textBox.value.add(false);
  }

}

}
void limiter(String value,TextEditingController controller, int pos){
// flutter bug https://github.com/flutter/flutter/issues/67236
String result;
bool backSpace = false;
result = value;
if(result == " " || result.contains(" ")){
  // send OSC space character
  print("space bar");
}

if(result.length == 1 ){
  result = result.substring(0,1);
} else if (result.length > 1) {
  result = result.substring(1);
}

if(result == ""){
   print("---------------BackSpace---------------------");
   backSpace = true;

    nodePosition = pos -1;

   if(nodePosition <  0){
     nodePosition = TOTAL_TEXT_BOX-1;
     FocusScope.of(context).requestFocus(focusNode[nodePosition]);
   }else {
     FocusScope.of(context).requestFocus(focusNode[nodePosition]);
   }

}
if(backSpace == false){
  print("------------no backspace-----------------");
  controller.text = result;
  controller.selection = TextSelection.fromPosition(TextPosition(offset: result.length));
} else {
  print("----------backspace-------------");
  controller.text = "⌫";
  controller.selection = TextSelection.fromPosition(TextPosition(offset: result.length));
}

}
void textBoxClicked(){
// prevent the user from clicking the text box that is not in focus
for(int x = 0; x < TOTAL_TEXT_BOX;  x++){
if(_textBox.value[x] == true){
focusNode[x].canRequestFocus = true;
} else if(_textBox.value[x] == false){
focusNode[x].canRequestFocus = false;
}
}
}
// just a snipet warp code in a Scaffold
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
      ValueListenableBuilder(
        builder:(BuildContext context, List textBoxList, Widget child) {
          return Row(children: [

            for(int x =0; x < TOTAL_TEXT_BOX;  x++) Container(
                width:50,
                height:50,
                child: Listener(
                  onPointerDown:(event){
                    print("touch");
                    textBoxClicked();
                  },
                  onPointerUp:(event){
                    print("touch");
                    textBoxClicked();
                  },
                  child: TextField(
                    enableInteractiveSelection: false,
                    focusNode:focusNode[x],
                    controller:textController[x],
                    autofocus: x==0? true:false,  //a look
                    style: TextStyle(color: Colors.white),
                    keyboardType: TextInputType.url,
                    decoration: InputDecoration(
                      labelStyle: TextStyle(color: Colors.white),
                      fillColor: Colors.white,
                      border: OutlineInputBorder(),
                    ),
                    onChanged:(value){
                      limiter(value, textController[x], x);
                      if(value.length >= 1){
                        setState(() {
                          nodeCount = x + 1;
                          if(nodeCount >= TOTAL_TEXT_BOX){
                            _textBox.value[0] = true;
                            _textBox.value[TOTAL_TEXT_BOX-1] = false;
                          } else {
                            _textBox.value[x] = false;
                            _textBox.value[nodeCount] = true;

                          }

                          print(_textBox.value);

                          if(nodeCount >= TOTAL_TEXT_BOX){
                            nodeCount = 0;
                            FocusScope.of(context).requestFocus(focusNode[nodeCount]);
                          } else {
                            FocusScope.of(context).requestFocus(focusNode[nodeCount]);
                          }
                        });
                      }
                    },
                  ),
                )
            ),

            SizedBox(width:5),

          ]);
        },
        valueListenable: _textBox,
      ),

`
My workaround is to create multiple text fields that take only one character. Once a character is inputted into the text field i will change another focus node. When I reach to the end of my focus node list I will loop back to the first node and overwrite the next. If a character is deleted I will be able to detect the backspace since my fields only take one character.  I denote backspace with "⌫" in my text field. If anyone needs help with my solution please ask :}  hope this helps.

My ultimate solution was to put a TextField into a 1x1 Container (needs to get rendered in order to get focused), then handle the onChanged from him. The inputs above this hidden TextField were implemented by hand.

Me too, has anyone found a solution?

I found a simply solution, use zero-width-space '\u200B' replace empty.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class EnglishPin extends StatefulWidget
{
    String word;

    EnglishPin({@required this.word});

    @override
    State<StatefulWidget> createState() => _EnglishPin();
}

class _EnglishPin extends State<EnglishPin>
{
    final space = '\u200B';

    final maxLineCount = 18;

    var mainAlignment = WrapAlignment.center;

    List<TextEditingController> controllerList;

    List<FocusNode> focusNodeList;

    @override
    void initState()
    {
        super.initState();

        controllerList = List<TextEditingController>.generate(widget.word.length, (index){
          return TextEditingController()..text="$space";
        });

        focusNodeList = List<FocusNode>.generate(widget.word.length, (index){
          return FocusNode();
        });

        if(widget.word.length > 18){
          if(mounted){
            setState(() {
              mainAlignment = WrapAlignment.start;
            });
          }
        }

    }

    @override
    void dispose()
    {
        controllerList.forEach((element) {
          element.dispose();
        });

        focusNodeList.forEach((element) {
          element.dispose();
        });

        super.dispose();
    }

    @override
    Widget build(BuildContext context)
    {
        return Wrap(
          alignment: mainAlignment,
          children: List<Widget>.generate(widget.word.length, (index) => buildInput(index)),
        );
    }

    Widget buildInput(int index)
    {
        return Padding(
          padding: EdgeInsets.all(1),
          child: ConstrainedBox(
            constraints: BoxConstraints.tight(const Size(18 , 50)),
            child: TextField(
              controller: controllerList[index],
              autofocus: index == 0 ? true : false,
              focusNode: focusNodeList[index],
              textAlign: TextAlign.center,
              onChanged: (str) => onchange(str, index),
              onTap: ()=>onTapFunc(index),
              decoration: InputDecoration(

              ),
              style: TextStyle(
                fontSize: 20
              ),
            ),
          ),
        );
    }

    //文字改变
    void onchange(String str, int index)
    {
        if (str.length >= 2) {

          if (str.length > 2) {
            var newStr = space + str.substring(str.length - 1, str.length);

            controllerList[index].value = TextEditingValue(
              text: newStr,
              selection: TextSelection.collapsed(offset: 2)
            );
          }

          if (index < widget.word.length -1) {
            focusNodeList[index + 1].requestFocus();
          }

          return;
        }

        if (str.length == 1 && str == space && index != 0) {
          focusNodeList[index - 1].requestFocus();
          return;
        }

        if (str.length <= 0) {
          controllerList[index].value = TextEditingValue(
            text: space,
            selection: TextSelection.collapsed(offset: 1)
          );

          if (index != 0) {
            focusNodeList[index - 1].requestFocus();
          }
        }
    }

    void onTapFunc(int index)
    {
        controllerList[index].selection = TextSelection.collapsed(offset: controllerList[index].text.length);
    }

}

Hi Swat ,
I have modified your code like this:
效果差不多， 处理好那个占位符就好了。
class MyPinCode extends StatefulWidget {
int  pinCnt;
MyPinCode(this.pinCnt);
@override
_MyPinCodeState createState() => _MyPinCodeState();
}
class _MyPinCodeState extends State {
final dummyChar = '\u200B';
List _pinCodeTxtCtrolorLs = [];
List _pinCodeTxtFNodeLs = [];
int _maxPinCount = 6;
@override
void initState() {
super.initState();
_maxPinCount = widget.pinCnt;

for (int i = 0; i < _maxPinCount; i ++) {
  _pinCodeTxtCtrolorLs.add(TextEditingController()
    ..text = dummyChar);
  _pinCodeTxtFNodeLs.add(FocusNode());
}

}
void _pinCodeTxtChged(String currStr, int idx ){
int _focusPos = 0;

if(currStr.length == 0){
  _focusPos = max(idx - 1 , 0);
  _pinCodeTxtCtrolorLs[idx].value = TextEditingValue(text: dummyChar , selection: TextSelection.collapsed(offset: 2));
  FocusScope.of(context).requestFocus(_pinCodeTxtFNodeLs[_focusPos]);
  return;
}

if(currStr.length == 2){
  _focusPos = min( idx + 1 , _maxPinCount - 1);
  _pinCodeTxtCtrolorLs[idx].value = TextEditingValue(text: dummyChar + currStr[1], selection: TextSelection.collapsed(offset: 2));
  FocusScope.of(context).requestFocus(_pinCodeTxtFNodeLs[_focusPos]);
  return ;
}

if(currStr.length > 2){
  _focusPos = min( idx + 1 , _maxPinCount - 1);
  _pinCodeTxtCtrolorLs[idx].value = TextEditingValue(text: dummyChar + currStr[currStr.length - 1], selection: TextSelection.collapsed(offset: 2));
  FocusScope.of(context).requestFocus(_pinCodeTxtFNodeLs[_focusPos]);
}

}
@override
Widget build(BuildContext context) {
return Material(
child: Wrap(
spacing: 10,
children: [
...[for(int i =0 ; i < _pinCodeTxtCtrolorLs. length ; i ++) i].map((idx) {
return SizedBox(width: 46,height: 46,
child: TextField(
controller: _pinCodeTxtCtrolorLs[idx],
focusNode:  _pinCodeTxtFNodeLs[idx],
textAlign: TextAlign.center,
showCursor: false,
selectionControls: null,
autofocus: idx == 0 ?  true : false,
keyboardType: TextInputType.number,
onChanged: (str) => _pinCodeTxtChged(str,idx) ,
)
);
}).toList(),
],),
);

}
}

I think this should now be possible with the Shortcuts API.  You can detect when the backspace key is pressed by overriding LogicalKeyboardKey.backspace in a Shortcuts widget and mapping it to a custom Intent and Action.
It's not yet available on stable.  You'll need commit 136da59 from https://github.com/flutter/flutter/pull/79973 at the oldest.
Let me know if I'm missing some functionality here and I'll reopen if so.

Full example code
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const MyHomePage(title: 'Detecting Backspace Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late final Iterable<FocusNode> _focusNodes;
  late final Iterable<TextEditingController> _controllers;

  // When backspace is pressed, move to the previous field.
  void _onBackspace() {
    Actions.invoke<DeleteTextIntent>(context, const DeleteTextIntent());
    if (_focusNodes.elementAt(0).hasFocus) {
      return;
    }
    if (_focusNodes.elementAt(1).hasFocus) {
      _focusNodes.elementAt(0).requestFocus();
    }
    if (_focusNodes.elementAt(2).hasFocus) {
      _focusNodes.elementAt(1).requestFocus();
    }
  }

  // When a character is entered, move to the next field.
  void _onChanged(String text, int index) {
    if (text.isEmpty) {
      return;
    }
    if (index > 1) {
      return;
    }
    _focusNodes.elementAt(index + 1).requestFocus();
  }

  @override
  void initState() {
    super.initState();
    _focusNodes = <FocusNode>[
      FocusNode(),
      FocusNode(),
      FocusNode(),
    ];
    _controllers = <TextEditingController>[
      TextEditingController(),
      TextEditingController(),
      TextEditingController(),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        // Remap the backspace key to our custom _PreviousFieldIntent. These
        // Shortcuts and Actions widgets should be placed only wrapping the
        // widgets where you want to intercept the backspace key.
        child: Shortcuts(
          shortcuts: <LogicalKeySet, Intent> {
            LogicalKeySet(LogicalKeyboardKey.backspace): const _PreviousFieldIntent(),
          },
          child: Actions(
            actions: <Type, Action<Intent>>{
              _PreviousFieldIntent: CallbackAction<_PreviousFieldIntent>(
                onInvoke: (_PreviousFieldIntent intent) => _onBackspace(),
              ),
            },
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: <Widget>[
                for (int i = 0; i < 3; i += 1)
                  SizedBox(
                    width: 100.0,
                    child: TextFormField(
                      controller: _controllers.elementAt(i),
                      focusNode: _focusNodes.elementAt(i),
                      maxLength: 1,
                      decoration: const InputDecoration(
                        counter: SizedBox.shrink(),
                      ),
                      onChanged: (String text) => _onChanged(text, i),
                    ),
                  ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class _PreviousFieldIntent extends Intent {
  const _PreviousFieldIntent();
}

@justinmc
Could you take a look at the provided code example, please?
The _onBackspace method never gets called. I tried to run the code, provided in your comment above, but it also does not work for me.
The app was compiled on iPhone 8 (real device).
flutter --version
Flutter 2.4.0-1.0.pre.180 • channel master • https://github.com/flutter/flutter.git
Framework • revision a268b011f3 (23 hours ago) • 2021-07-11 03:56:04 -0400
Engine • revision 06b7d1f8c6
Tools • Dart 2.14.0 (build 2.14.0-301.0.dev)
`import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class VerifyPhoneInput extends StatefulWidget {
const VerifyPhoneInput({Key? key}) : super(key: key);
@override
_VerifyPhoneInputState createState() => _VerifyPhoneInputState();
}
class _VerifyPhoneInputState extends State {
static const int _codeInputSectionsCount = 4;
final List _codeInputControllers = [];
final List _codeInputFocusNodes = [];
@override
void initState() {
super.initState();
for (int i = 0; i < _codeInputSectionsCount; i++) {
_codeInputControllers.add(TextEditingController());
_codeInputFocusNodes.add(FocusNode());
}
}
@override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.backspace): const _PreviousFieldIntent(),
},
child: Actions(
actions: <Type, Action>{
_PreviousFieldIntent: CallbackAction<_PreviousFieldIntent>(
onInvoke: (_PreviousFieldIntent intent) => _onBackspace(),
),
},
child: GridView.builder(
shrinkWrap: true,
itemCount: _codeInputSectionsCount,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
),
itemBuilder: (BuildContext context, int index) => _buildCodeInputRectangle(index),
),
),
);
}
Widget _buildCodeInputRectangle(int index) {
return TextField(
maxLength: 1,
controller: _codeInputControllers[index],
focusNode: _codeInputFocusNodes[index],
onChanged: (code) => _onCodeChanged(code, index),
);
}
void _onBackspace() {
print('call _onBackspace');
Actions.invoke(context, const DeleteTextIntent());
}
void _onCodeChanged(String code, int index) {
_codeInputFocusNodes[index].unfocus();
if (code.isNotEmpty) {
if (index < _codeInputSectionsCount - 1) {
_codeInputFocusNodes[index + 1].requestFocus();
if (_codeInputControllers[index + 1].text.isNotEmpty) {
_codeInputControllers[index + 1].selection = TextSelection(
baseOffset: 0,
extentOffset: 1,
);
}
}
} else {
if (index > 0) {
_codeInputFocusNodes[index - 1].requestFocus();
}
}
}
}
class _PreviousFieldIntent extends Intent {
const _PreviousFieldIntent();
}`

My fault, works for a hardware keyboard, not a software keyboard 😢

@justinmc , could you tell, please, what is the approximate date the feature will be ready for the software keyboard?

@yaroslav-kahaniak Unfortunately I don't know any way to achieve this in Flutter, so I can't say if or when we can add it to the framework.

The problem is that soft keyboards aren't really keyboards, they're touch screen apps that act like keyboards. Consequently, they don't send key events, they send text editing streams, and so naturally they don't send anything at all when a delete has been pressed on an empty field, since no edit occurs in that case.

is this case already solve? if yes, can someone who already solved it share how to fix it? thanks

This issue was opened in 2018. It's 2021 now and it's still open. @Flutter team.. Please add this feature. We're not asking for much I guess.

Hi guys,
Like many of you here I did not find the solution so created a quick workaround and sharing here if anyone urgently needs a solution.
`class TestTextField extends StatefulWidget {
const TestTextField({Key? key}) : super(key: key);
@override
_TestTextFieldState createState() => _TestTextFieldState();
}
class _TestTextFieldState extends State {
final TextEditingController? _controller_0 = TextEditingController();
final TextEditingController? _controller_1 = TextEditingController();
final TextEditingController? _controller_2 = TextEditingController();
final TextEditingController? _controller_3 = TextEditingController();
final TextEditingController? _controller_4 = TextEditingController();
final TextEditingController? _controller_5 = TextEditingController();
final TextEditingController? _mainController = TextEditingController();
final FocusNode? _mainFocusNode = FocusNode();
@override
void initState() {
super.initState();
}
@override
void dispose() {
_controller_0?.dispose();
_controller_1?.dispose();
_controller_2?.dispose();
_controller_3?.dispose();
_controller_4?.dispose();
_controller_5?.dispose();
_mainController?.dispose();
_mainFocusNode?.dispose();

super.dispose();

}
@override
Widget build(BuildContext context) {
return Center(child: _buildOTPInputView());
}
Widget _buildOTPInputView() {
return Container(
child: Stack(
children: [
_buildOTPView(context),
Container(
color: Colors.transparent,
width: MediaQuery.of(context).size.width * 0.9,
height: 72,
padding: EdgeInsets.only(left: 10),
child: TextFormField(
controller: _mainController,
focusNode: _mainFocusNode,
onChanged: (value) {
if (value.length == 0) {
_controller_0?.text = '';
_controller_1?.text = '';
_controller_2?.text = '';
_controller_3?.text = '';
_controller_4?.text = '';
_controller_5?.text = '';
}
if (value.length == 1) {
_controller_0?.text = value[0];
_controller_1?.text = '';
_controller_2?.text = '';
_controller_3?.text = '';
_controller_4?.text = '';
_controller_5?.text = '';
}
if (value.length == 2) {
_controller_0?.text = value[0];
_controller_1?.text = value[1];
_controller_2?.text = '';
_controller_3?.text = '';
_controller_4?.text = '';
_controller_5?.text = '';
}
            if (value.length == 3) {
              _controller_0?.text = value[0];
              _controller_1?.text = value[1];
              _controller_2?.text = value[2];
              _controller_3?.text = '';
              _controller_4?.text = '';
              _controller_5?.text = '';
            }

            if (value.length == 4) {
              _controller_0?.text = value[0];
              _controller_1?.text = value[1];
              _controller_2?.text = value[2];
              _controller_3?.text = value[3];
              _controller_4?.text = '';
              _controller_5?.text = '';
            }

            if (value.length == 5) {
              _controller_0?.text = value[0];
              _controller_1?.text = value[1];
              _controller_2?.text = value[2];
              _controller_3?.text = value[3];
              _controller_4?.text = value[4];
              _controller_5?.text = '';
            }

            if (value.length == 6) {
              _controller_0?.text = value[0];
              _controller_1?.text = value[1];
              _controller_2?.text = value[2];
              _controller_3?.text = value[3];
              _controller_4?.text = value[4];
              _controller_5?.text = value[5];
            }
          },
          maxLength: 6,
          showCursor: false,
          decoration: InputDecoration(
            hintText: '',
            border: InputBorder.none,
            counterText: '',
          ),
          keyboardType: TextInputType.phone,
          style: TextStyle(color: Colors.transparent),
        ),
      )
    ],
  ),
);

}
Widget _buildOTPView(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width * 0.9,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildOTPTextField(controller: _controller_0),
_buildOTPTextField(controller: _controller_1),
_buildOTPTextField(controller: _controller_2),
_buildOTPTextField(controller: _controller_3),
_buildOTPTextField(controller: _controller_4),
_buildOTPTextField(controller: _controller_5),
],
),
);
}
Widget _buildOTPTextField({
TextEditingController? controller,
}) {
return Container(
width: 48,
height: 72,
color: Colors.white,
alignment: Alignment.center,
child: TextFormField(
controller: controller,
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
style: TextStyle(fontSize: 32),
showCursor: false,
maxLength: 2,
decoration: InputDecoration(
hintText: '0',
hintStyle: TextStyle(color: Colors.grey, fontSize: 32),
border: InputBorder.none,
counterText: '',
),
),
);
}
}
`

@justinmc what the host system (let's say Android) emits once you press the "delete" button? Does the system simply gives us the original string minus one character?
Sorry it has been a while since I checked this issue and I forgot the main reason for this issue to exist 😄

Since this issue is still getting views and responses, and it looks like people may be ignoring @gspencergoog's explanation, I drafted the following post on this topic:
https://medium.com/super-declarative/why-you-cant-detect-a-delete-action-in-an-empty-flutter-text-field-3cf53e47b631
I recommend either redefining this issue to an actionable problem statement, or closing it. Presently, the conversation in this ticket looks like it expects a change that's technically impossible to deliver.

Thanks @matthew-carroll! That's an excellent explanation of the problem.

I think now we can lock this thread and point everyone to Matthew's article for a better understanding of the problem since this issue can't be fixed by Flutter.

Brother

I'm attempting to make a "CodeEntry" widget, consisting of 4 separate TextFields that accept a single digit each:

It should auto focus the next fields as it's being filled out, and move to the previous ones if being deleted.
The problem occurs when a new field is focused and 'Delete' is immediately typed. It should move back to previous field. It will not work since the onChanged is not called.
Any ideas?

Brother https://pub.dev/packages/pin_code_fields/example this will solve your purpose

I think now we can lock this thread and point everyone to Matthew's article for a better understanding of the problem since this issue can't be fixed by Flutter.

This implementation breaks the sentence capitalization. On iOS the keyboard will not suggest a capital letter for the beginning of the sentence if the whitespace is inserted at the start.

I'm convinced that we should close this, thanks @matthew-carroll.
See https://github.com/flutter/flutter/issues/14809#issuecomment-893918250 for how to achieve this on software keyboards and https://github.com/flutter/flutter/issues/14809#issuecomment-877506427 for hardware keyboards.
Unfortunately it seems like capitalization on iOS is another hurdle for the zero width character solution, but it doesn't seem like something Flutter should fix itself.

@kasyr You can try adding an initial real space to the text, but then using a custom TextEditingController with buildTextSpan overridden to swap the initial real space with a zero width space for display.

I'm able to detect the backspace keypress on an empty text field using RawKeyboardListener. The thing is it will detect the backspace key press if the text field is empty.
RawKeyboardListener(
autofocus: true,
focusNode: FocusNode(),
onKey: (event) {
if (event.data.logicalKey.keyId ==<PHONE_NUMBER>)
setState(() {
bsCount++;
});
},
child: TextField(),
),

On Android many soft keyboards (including Gboard's numpad mode, see https://github.com/flutter/flutter/issues/75616) send the backspace key as a hardware key event, and on iOS we have https://developer.apple.com/documentation/uikit/uikeyinput/1614572-deletebackward. So we should be able to do this on at least iOS and Android. On Android I think the text field is already getting a delete intent when the backspace key is pressed on one of those soft keyboards and on iOS we should also be able to convert the deleteBackward call into a key event? @gspencergoog

Is that true for older releases of GBoard? I'm pretty sure I tested it and found that GBoard didn't send any events for that in the past. And even if it does, it'll mean that we are depending on the behavior of the keyboard installed by the user/OEM to make it work.
I mean, if you think we can cover most of the use cases (like 90%), then sure, we should try and make this work, but otherwise I think it's a recipe for frustration, where it works for some users and not for others. Not that the status quo isn't frustrating (it certainly is), but at least it's consistent...

I just quickly tried this on the Android emulator and couldn't see a DeleteTextIntent get fired when I pressed the soft keyboard backspace key with keyboardType: phone (or number, or none).  Android API 30 and Gboard 9 (also MS Swift Key since I had it installed, same result).  Am I doing that right?

I think at least with the "extra symbol" Gboard keypad that should be the case (I tested while I was attempting to fix https://github.com/flutter/flutter/pull/75668). It seems to make sense for the virtual keyboards to do so for a regular Android app to allow the app to detect a delete event in an empty input field.


added some basic formatting and sectioning

The rainbow pic is a little big. Can we shrink it? Or, what do you think about this pic?

Another suggestion would be to post some screenshots here that exhibit the changes made.


fix(NumberInput): cope with user events being referred in async code

Fixes #566.
Changelog
New

<NumberInput> code to retain event after the event loop to cope with async event firing.

Thank you for pointing this out @tw15egan! Will address it next week.

Any Update, eta on integrating fix into released code?

Set the <input> as "imaginary target" to the evens being fired: https://github.com/carbon-design-system/carbon-components-react/pull/597/commits/4060efb326f9084cf52ace40eac28a705038cfbd. Hope this works for the use case of the original issue.

Maybe I'm doing this wrong, but I am still unable to get the value of the NumberInput onchange or onclick, either returns the svg / path / button or null

@tw15egan Yes - I tried to keep it as natural as handling events on <input>, but please feel free to suggest for an alternative. Thanks!

As long as we can ensure users know they need to access evt.imaginaryTarget.value to get the value of the input, I'm good with it! Thanks for tackling this , @asudoh 🙂

Coming in late to this, could we just update this block so that they can access the value?
  handleChange = evt => {
    if (!this.props.disabled) {
      this.setState(
        {
          value: evt.target.value,
        },
        () => {
          this.props.onChange({ value: evt.target.value });
        }
      );
    }
  };

Could even do:
this.props.onChange({ target: { value: evt.target.value }});

If we wanted consumers to think they were still accessing the event value.

@joshblack Good question - Just didn’t want to lose any other values in event, but please feel free to make a change if you feel that value is the only thing consumers would need. Thanks!

Any input here before merging @joshblack ?

It does feel like we should do the simpler thing here and not persist the event if we can get away with it. Are there strong use-cases for including other event fields for consumers that would require us persisting the event?

@joshblack min/max/validity are some of those properties consumers would be interested in. In a bigger thought, I attempted ensure that the fired event works like what <input type=“number”> fires on stepping up/down. You may know better on the best practice on this space, and please feel free to bring up a counterproposal. Thanks!


[e2e]: go release main config-ldflags-noassets slsa3

Repo: https://github.com/slsa-framework/example-package/tree/v17.0.370
Run: https://github.com/slsa-framework/example-package/actions/runs/5250489424
Workflow file: https://github.com/slsa-framework/example-package/tree/main/.github/workflows/e2e.go.release.main.config-ldflags-noassets.slsa3.yml
Workflow runs: https://github.com/slsa-framework/example-package/actions/workflows/e2e.go.release.main.config-ldflags-noassets.slsa3.yml
Trigger: release
Branch: v17.0.370
Date: Tue Jun 13 02:17:19 UTC 2023

Repo: https://github.com/slsa-framework/example-package/tree/v17.0.370
Run: https://github.com/slsa-framework/example-package/actions/runs/5250489424
Workflow file: https://github.com/slsa-framework/example-package/tree/main/.github/workflows/e2e.go.release.main.config-ldflags-noassets.slsa3.yml
Workflow runs: https://github.com/slsa-framework/example-package/actions/workflows/e2e.go.release.main.config-ldflags-noassets.slsa3.yml
Trigger: release
Branch: v17.0.370
Date: Tue Jun 13 03:53:02 UTC 2023
Tests are passing now. Closing this issue.


Splitter and splitter-branch tiles

Hi,
Could you please provide real-world scenarios for using Splitter and splitter-branch tiles?

Document Details
⚠ Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

ID: 1812977d-5a0b-819d-d932-99a32526d5fc
Version Independent ID: 8dd12914-8242-8758-8887-3fbe4ad2d3aa
Content: Customer-journey tiles (Dynamics 365 for Marketing)
Content Source: ce/marketing/customer-journey-tiles-reference.md
Service: dynamics-365-marketing
GitHub Login: @kamaybac
Microsoft Alias: kamaybac

Hi Frasse, Thank you for your input. I'll add this to our to-do list for this topic. These tiles work by splitting contacts in a segment, with contacts selected randomly to be sent down each path.
Some examples for how to use this could include A/B testing (send, for example, half of the segment email design A and the rest design B and then look to see which gets more opens and clicks). Or maybe to run a contest, where just one contact selected at random goes down one path to receive an email announcing the prize.

Hi @tommylauritzen. Unfortunately no, we don't have that functionality yet, which is why we don't claim to have a full a/b testing feature.


SystemParamBuilder - Enable type inference of closure parameter when building dynamic systems

Objective
When building a system from SystemParamBuilders and defining the system as a closure, the compiler should be able to infer the parameter types from the builder types.
Solution
Create methods for each arity that take an argument that implements both SystemParamFunction as well as FnMut(SystemParamItem<P>,...).  The explicit FnMut constraint will allow the compiler to infer the necessary higher-ranked lifetimes along with the parameter types.
I wanted to show that this was possible, but I can't tell whether it's worth the complexity.  It requires a separate method for each arity, which pollutes the docs a bit:

Example
let system = (LocalBuilder(0u64), ParamBuilder::local::<u64>())
    .build_state(&mut world)
    .build_system(|a, b| *a + *b + 1);

do you want to try the fake_variadic trick here and see if this resolves the docs regression?

I'm happy to try it!  I don't expect it to work, though.  I think that only works for trait impls, and this is a bunch of impl blocks.
How do I test that?  Just running cargo doc doesn't seem to get me any collapsed impls, even for traits where I see them collapsed on dev-docs.bevyengine.org.

@bash, the author of #14703 may have advice for you there.

How do I test that? Just running cargo doc doesn't seem to get me any collapsed impls, even for traits where I see them collapsed on dev-docs.bevyengine.org.

You can build the docs with fake_variadic enabled by using the nightly compiler and the correct RUSTFLAGS / RUSTDOCFLAGS (See https://github.com/bevyengine/bevy/blob/main/.github/workflows/docs.yml#L59).
This should do the trick:
RUSTFLAGS='--cfg docsrs_dep' RUSTDOCFLAGS='--cfg=docsrs' cargo +nighty doc

Let me know if you want to experiment more, or if I should merge this as is. If I don't hear back I'll merge this on Monday :)

Nope, putting the attribute on the impl block fails with
error: `#[doc(fake_variadic)]` must be used on the first of a set of tuple or fn pointer trait impls with varying arity

so I don't think it will work for this.
(And the reason I had trouble before is that I forgot how to set environment variables in PowerShell :).)
I can make it work for impl<P: SystemParam, B: SystemParamBuilder<P>> SystemParamBuilder<(P₁, P₂, …, Pₙ)> for (B₁, B₂, …, Bₙ), though, which got missed because the SystemParamBuilder and fake_variadic PRs were open at the same time.  I'll push up a separate PR for that!


Get-Credential password confirmation

Summary of the new feature/enhancement
Passwords are hard. When we prompt for passwords, especially for complex passwords, it's important to ensure that we know what we are entering. When using Get-Credential interactively from the command line, you currently only get one opportunity to type in your password. It's easy to fat finger a complex password.  We should enable a confirmation of the password when using this cmdlet interactively.
Proposed technical implementation details
Consider adding a -ConfirmPassword parameter that confirms two password strings entered at the command line are correct. If correct, proceed with saving the credential. If incorrect, re-prompt the user for matching strings. Something like this:
Successful entry
PS C:\> Get-Credential -credential user1 -ConfirmPassword

PowerShell credential request
Enter your credentials.
Password for user user1: ************
Confirm password for user user1: ************

UserName                     Password
--------                     --------
user1    System.Security.SecureString

Unsuccessful entry
PS C:\> Get-Credential -credential user1 -ConfirmPassword

PowerShell credential request
Enter your credentials.
Password for user user1: *****
Confirm password for user user1: ************

Passwords do not match.
Enter your credentials.
Password for user user1: ************
Confirm password for user user1: ************

UserName                     Password
--------                     --------
user1    System.Security.SecureString

-Confirm would conflict with the common parameter used for ShouldProcess.
I'd suggest something like -ConfirmPassword

Good idea @vexx32, I updated the description to reflect that.

How would it know how to verify the credential? I realize that the request is probably for Active Directory credentials but a credential object can be used for anything.
I think it would make more sense to break this functionality out into a specific command, something like Test-ADCredential or a completely separate Get-ADCredential that wraps Get-Credential.

It's not a verification of the credential itself. It's a confirmation that the password you entered twice matches. This would be similar to many web site credential creation procedures that are commonly seen, as well as the passwd command on Linux.
[root@vm1~]# passwd user1
Changing password for user user1.
New UNIX password:
Retype new UNIX password:
passwd: all authentication tokens updated successfully.

That said, I think having an easy way to validate an AD credential could be valuable as well @SeeminglyScience, but that's outside the scope of this particular enhancement request.

It's not a verification of the credential itself. It's a confirmation that the password you entered twice matches.

Ahhh yes, I misunderstood 🙂  that makes much more sense 👍

Does anyone want to implement this? It looks like easy and very useful enhancement.

I wouldn't mind giving this a shot myself, but this would be my first contribution so I'd likely need some hand-holding. I'd guess that the logic would be implemented here:
https://github.com/PowerShell/PowerShell/blob/74517923cfa26244662ccbe995e7f88fe06c1f02/src/Microsoft.PowerShell.Security/security/CredentialCommands.cs#L93
Not entirely sure how to handle the UI elements, or adding a parameter to the cmdlet.

@jboeshart Parameters is defined above in the code with Parameter attribute.
Use debugger to get better understanding how the code works.

Gotcha, let me hack away at it and see if I can get something workable here. Are contributors also responsible for creating/updating tests too? Just want to make sure I included everything that's necessary.

Search public override PSCredential PromptForCredential in
https://github.com/PowerShell/PowerShell/blob/1be3f4cc0e465ae11ad8e59e9060f5a59e4762eb/src/Microsoft.PowerShell.Security/security/CredentialCommands.cs
Yes, you should update tests in https://github.com/PowerShell/PowerShell/blob/acb52b3d9c92e347ea529242e8ab25a09ea31222/test/powershell/Modules/Microsoft.PowerShell.Security/GetCredential.Tests.ps1

Tests can be tricky. We use special test host (created in the test file with $th = New-TestHost). I guess you will have to update it too. See help module in test\tools\Modules\HelpersHostCS\

Ok, I've logged #10692 with my updates to make this work. Need to work on tests, but I wanted to get some feedback on the changes that I've made.
I'd also like some clarity on if the code to compare the secure strings is acceptable. This is a bit sensitive, since you can't directly compare secure strings. The code is based on guidance from the following links. I think this is probably ok, since we're just comparing strings, and the only way they would be exposed is if you were actively debugging the code as you were entering the password. That said, I'd like to have someone with a little more experience in this area validate this is ok.
https://stackoverflow.com/questions/4502676/c-sharp-compare-two-securestrings-for-equality
https://www.sjoerdlangkemper.nl/2017/11/08/comparing-securestrings-in-dotnet/

-ConfirmPassword does still intersect with -Confirm common parameter.
I suggest -ReEnterPassword.

@iSazonov I would like to try and fix this issue. This is my first fix and so I may need little help with the process

@Shriram0908 Feel free to ask.

@iSazonov I see "Expected — Waiting for status to be reported" on the PR. What does it mean and should I do something from my side

@iSazonov Can we re-open this issue and How shall we implement this feature ?

@Shriram0908 You need to review related discussions like https://github.com/PowerShell/PowerShell/issues/13346#issuecomment-668322559
I think @rjmholt could help you too.

We just need to come up with a better way to re-prompt a password. There are a few possibilities:

We have a new non-abstract method on PSHostUserInterface that re-prompts
We use a configuration specific to the ConsoleHostUserInterface to allow it to re-prompt, without affecting other hosts that may also choose to implement it
We decide that re-prompt is above the PSHostUserInterface's level and simply call the relevant prompt method multiple times

(1) provides the most re-use, but is hardest to get right. One way is to provide a new method that re-prompts, itself calling one of the abstract methods, but meaning that commands not targeting PS 7.1 won't be able to re-prompt. Because commands need to call the overridden abstract method directly, we're quite constrained in our options.
(2) is essentially a bad implementation of (1), so is probably not the right way to go.
(3) is another possibility, which means we're more free to implement what we like, but means that other hosts (particularly the PowerShell extension for VSCode) won't get this improvement.

I am new and first time contributor,  can i try ?

@RathiJ Yes. Please read the issue history because we had a compatibility problems with "right" implementation.

@iSazonov  ,  Thank you , can you please assign this bug for me?, I read threads above , got some history of it.  I am working on it. Thanks again.

@RathiJ GitHub does not allow explicitly assign an issue for non-members but feel free to work on the issue.

sure , thank you


Build failure: mysql-workbench-8.0.38

Steps To Reproduce
Steps to reproduce the behavior:

build mysql-workbench-8.0.38

Build log

Build Log
https://gist.github.com/khaneliman/88835bd1260352ba17cd1430d9a0dffd

Additional context

I get same error even after bumping to 8.0.40
Metadata

Notify maintainers
@TomaSajt

Note for maintainers: Please tag this issue in your PR.

Add a :+1: reaction to issues you find important.

Reverting the latest swig bump worked for me (it was included in the latest staging-next -> master merge)

Added a commit to my refactor PR: https://github.com/NixOS/nixpkgs/pull/344547

Fixed via https://github.com/NixOS/nixpkgs/pull/344547


Incorrect address

I’m a developer and just looked your excellent demo site.
But I have one question.
When I connect wallet using joyid, it shows incorrect address.
I attached screenshot. first image is from https://testnet.joyid.dev/ and I think it’s correct address.
What’s the reason? And how to mint spore on nervous main net work with correct address?
Thanks.

@ahonn @ShookLyngs    could you reply?

Hi @GeraldoMarques1, thanks for your feedback.
Spore-demo is integrated with JoyID through Omnilock for now, which means it follows the same approach as MetaMask.
So it is different from the address seen in JoyID (JoyID uses its own lock), you can understand that in the spore demo we are only using JoyID to sign transactions.
We plan to temporarily disable JoyID in the demo to avoid confusion. It will be re-added when we can fully support JoyID.
We will explain this situation in the README. At the same time, if you are developing Spore DApp, the Omnilock integration is not recommended.

@ahonn   thanks for your response.
I understand, and I'm trying to integrate 'Mint Spore' on my dApp using joyid and nervous network.
So I can get ethAddress and ckb address using '@joyid/ckb'  but I want to get correct lock.
Could you tell me how can I mint spore?
thanks.

@ahonn thanks for your response. I understand, and I'm trying to integrate 'Mint Spore' on my dApp using joyid and nervous network. So I can get ethAddress and ckb address using '@joyid/ckb' but I want to get correct lock. Could you tell me how can I mint spore? thanks.

You can refer to https://github.com/sporeprotocol/spore-demo/issues/69. ckb-dao-cobuild-poc's demo showcasing the management of DAO deposits through the use of a new transaction building protocol named Cobuild. In the poc demo, we have implemented the use of JoyID to manage DAO. You can management spores at same way.

ok, got it.
I wanted to know how to get joyid lock from ckb address

ok, got it. I wanted to know how to get joyid lock from ckb address

If you mean "get the lock script of a JoyID wallet address", you can use the parseAddress API from lumos:
import { helpers, config, Script } from '@ckb-lumos/lumos';

const joyIdWalletMainnetAddress = 'ckb...';
const lockScript: Script = helpers.parseAddress(address, {
  config: config.MAINNET,
});

console.log(lockScript); // { codeHash: ..., hashType: ..., args: ... }

yes. so I tested.
my test ckb address:
ckt1qrfrwcdnvssswdwpn3s9v8fp87emat306ctjwsm3nmlkjg8qyza2cqgqqytg65tdzm9prtw3rkjlnk80dyt326sf9sawqks7
and lock is
args: "0x0001168d516d16ca11add11da5f9d8ef6917156a092c"
codeHash: "0xd23761b364210735c19c60561d213fb3beae2fd6172743719eff6920e020baac"
hashType: "type"
but when I mint spore using this address and lock with createSpore from 'spore-sdk/core', I have error.
it's related to spore sdk? or any argument is wrong?

and lock is args: "0x0001168d516d16ca11add11da5f9d8ef6917156a092c" codeHash: "0xd23761b364210735c19c60561d213fb3beae2fd6172743719eff6920e020baac" hashType: "type"
but when I mint spore using this address and lock with createSpore from 'spore-sdk/core', I have error. it's related to spore sdk? or any argument is wrong?

This is a JoyID lock address, and I assume that when you try to construct a transaction and include this address in the fromInfos, it throws an error with a message similar to "not enough capacity in from infos!" If that's the case, the error is caused by Lumos, indicating that the JoyID lock is not officially supported by Lumos.
The cause of the error is that Lumos only collects capacity from known lock scripts (such as the default lock and the Omnilock). If you include a JoyID address in the fromInfos, Lumos doesn't recognize the JoyID lock script, and as a result, it cannot complete the capacity collection process. This is why the error is thrown.
As far as I know, the Lumos team is still working on supporting the JoyID lock, so a temp solution is to register a CustomLockScriptInfo in your dapp to handle capacity collection and witness-related fields for the JoyID lock script. However, it may present some difficulties. You can refer to the following example in Lumos on how to register a CustomLockScriptInfo : https://github.com/ckb-js/lumos/blob/ad483d9cb55ad38db9604fde456033b9513bc7f1/packages/common-scripts/examples/custom_lock_script_info/custom_lock_script_info.ts

As far as I know, the Lumos team is still working on supporting the JoyID lock

Looks like it :) for reference: https://github.com/ckb-js/lumos/issues/640


Macro in Arguments

Description
Macros in arguments are not parsed correctly
Code example

#define P(ptrtype) ptrtype *
void test(P(int) a);

Expected result
/*!*****************************************************************************
 * \brief
 *
 * \param a
 ******************************************************************************/

Actual result
/*!*****************************************************************************
 * \brief
 *
 ******************************************************************************/

This is obviously a bug, but I'm not sure it is one we can fix.
Since Microsoft does not expose the code model through their native language server it's not possible to have stuff like this. Same reason we can not have documentation on mouseover.
Only solution I think here would be to use an alternative language server for a clang codemodell and try to parse it this way.

The type is not used in the output. Can it be just skipped?
This might be useful: https://github.com/christophschlosser/doxdocgen/pull/23#issuecomment-341932606

Well since macros are just some identifier or some identifier followed by parenthesis we could parse this. But like I said in the @kamilpok references this might not be inline with what is actually going on and since the macro can basically do anything.

To be honest, I don't think there is a way to do this without having a codemodel available.
The macro you wrote could also be like this:
#define P(type, var) type* var
void foo(P(myType, a));

// or
#define P(var, type) type* var
void foo(P(a, myType));

There may be a possibility to have a guess for standard types but as soon as you get to custom types there is just no way to find out what is what by simply parsing the method signature string.
Macros are rarely defined in place as well and without a codemodel the addon can not access other documents except for the open ones.


⚠ Bofrost.be has degraded performance

In 7859c88, Bofrost.be (https://www.bofrost.be) experienced degraded performance:

HTTP code: 200
Response time: 1992 ms

Resolved: Bofrost.be performance has improved in e1910ba.


Cloud Code Mandrill with parse server giving error.

The other simple cloud functions are working fine after push from 'cloud/main.js' to heroku server
But when I add mandrill script it gives an error on app end as:
[Error]: Invalid function. (Code: 141, Version: 1.13.0)
I have installed Mandrill version '~0.1.0' into parser-server-example folder after adding the mandrill version into package.json file and push the code to heroku. But still the issue is same. Here is my cloud code:
`Parse.Cloud.define("sendMail", function(request, response) {
                   var Mandrill = require('mandrill');
                   Mandrill.initialize('xxxxxxxxxx');

               Mandrill.sendEmail({
                                  message: {
                                  text: "ffff",
                                  subject: "hello",
                                  from_email<EMAIL_ADDRESS>                                  from_name: "pqr",
                                  to: [
                                       {
                                       email<EMAIL_ADDRESS>                                       name: "trump"
                                       }
                                       ]
                                  },
                                  async: true
                                  },{
                                  success: function(httpResponse) {
                                  console.log(httpResponse);
                                  response.success("Email sent!");
                                  },
                                  error: function(httpResponse) {
                                  console.error(httpResponse);
                                  response.error("Uh oh, something went wrong");
                                  }
                                  });
               });`

Please let me know if there is anything wrong?
I have tried to install mandrill latest version but parse server does not installing the versions other than ~0.1.0

Unfortunately we can't support Mandrill specific issues here. If you do find a solution, though, please post it so that others can benefit it. You might have some luck by catching and logging all exceptions, updating your dependencies, and logging more errors from your app.


use jdk21

Description
Use jdk 21 instead of 17

I guess you checked the rest of the dependencies (especially Spring Boot) for JDK 21 compatibility, right?

Didn't check every single dependency but for functionality and Spring-Boot and Spring-Cloud support each at least jdk 21.


Throw error when WebView is missing

Ideally on any platform

system webview is:

in macOS, an integral part of the system and is not removable
in Linux, bundled inside the AppImage
in Windows, uninstallable but is installed by default
so, only windows requires treatment when webview is missing. relevant microsoft documentation: https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution

I'm thinking installing WebView2 Runtime after asking the user if it is not present

I'm thinking installing WebView2 Runtime after asking the user if it is not present

By "installing" you mean running the official installer as headless? I'm thinking that we could just give the user the installer link, so that we don't have to deal with

system files
admin permissions
probable restart after installation

WebView2 Runtime installers are designed to run headlessly for this exact purpose so it should be fine?

I thought that if we use the headless part, we still have to get the admin perm and handle restart ourselves. But I guess you're right, we can test and see.


Why typescript

Why is typescript being used and how should it be implemented.  there is no angular-spinner.js in this file.

Hi @bearcatFulton ,
I believe that TypeScript is better for the development process, and it's not related to the usage of the plugin since the TypeScript source code is being transpiled finally to a JavaScript code.
If you're asking from the perspective of developer, I believe that TypeScript is more powerful supplying all the ESNext language support with a Typing solid system, which we see that all the community is attracted to (Angular2+ is built upon TypeScript, React folks is starting to use flowjs static type checker, and even one of the most popular state management libraries for React is built on TypeScript which is called Mobx)
(You can read more on that, and I can reference a few great articles on that)
If you're asking from the perspective of user, there will be no changes at all! when we release the new package in the next days, you'll see just JavaScript codes in the node_modules/angular-spinner to link with them, nothing more
For more details, please review the README.md changes

Second,
angular-spinner.js is not pushed into the git because it's generated output file.
For more read this: https://medium.com/@kentcdodds/why-i-don-t-commit-generated-files-to-master-a4d76382564

I'll close the issue, Feel free to reopen if needed :wink:

I believe that without pushing generated .js file in this case forces developers which are using this to update their (stable) build systems which will make upgrade from 0.8.1 -> 1.x (and next versions) pain so I'm giving 👍  for pushing generated .js file to this repo.

Thanks @slavede for participating in this discussion.
What is exactly the pain?
npm install --save angular-spinner
and
<script src="node_modules/angular-spinner/dist/index.js"></script>

?

But when I initially do bower install I do not get node_modules folder.

Yes. The publish to bower packages will be done today, it's still linked to the old bower package (0.8.x).
Let me update you with the progress later today :smiley:

Ooooh, ok then. I thought it will stay this way for bower too. Thanks for the explanation :)

@urish , Give me permissions for bower package to publish

@urish @Attrash-Islam
While running

bower install --save angular-spinner

this happens:
bower                        ENOTFOUND Package angular-spinner not found

Since I manage all my dependencies with bower as well, this is not working.
Would it be possible for you to publish it ASAP?

Absolutely @arturskeeled , I'll take care of that ASAP.
Thanks for reporting!

Fixed. bower install as you like (v.1.0.1)


send dispatch to lavinmq-website on new release

WHAT is this pull request doing?
Sends dispatch action to lavinmq-website when new release is created, so that lavinmq-website automatically can update it's latest stable version info.
To be used with: https://github.com/84codes/lavinmq-website/pull/48
Needs a token to be able to communicate with lavinmq-website repo
HOW can this pull request be tested?
Specs? Manual steps? Please fill me in.

We also want to build new docs (https://github.com/84codes/lavinmq-docs) on a new release, so here's an idea:
Send the dispatch from this repo to https://github.com/84codes/lavinmq-docs and in the docs repo, we send a dispatch to the website repo, to trigger a new build. The website repo can continue to ingest the version file (https://docs.lavinmq.com/latest_release.json) from the docs repo at build time.
WDYT?

We also want to build new docs (https://github.com/84codes/lavinmq-docs) on a new release, so here's an idea:
Send the dispatch from this repo to https://github.com/84codes/lavinmq-docs and in the docs repo, we send a dispatch to the website repo, to trigger a new build. The website repo can continue to ingest the version file (https://docs.lavinmq.com/latest_release.json) from the docs repo at build time.
WDYT?

Interesting!
Yeah, we should definitely trigger docs build from here as well, but I'm not sure if I like the website having to depend on the docs. What if docs for some reason is unavailable during website build for example, or docs fail to build.
I think dispatching the version number from here should be more resilient, no?

I'm not sure if I like the website having to depend on the docs. What if docs for some reason is unavailable during website build for example, or docs fail to build.

I wouldn't worry about that too much, the docs site is hosted on GitHub Pages, like the website is, so they would probably be unavailable at the same time. If the docs can't build, we would likely want to address that ASAP anyway.

I think dispatching the version number from here should be more resilient, no?

We do get a longer chain with my idea (lavinmq -> docs -> website) but I like it because it means we can continue with the current solution of consuming the versions file at (website) build time, remove the javascript code about it, and avoid the need to make a (automated commit) to the website repo on every new release.

We do get a longer chain with my idea (lavinmq -> docs -> website) but I like it because it means we can continue with the current solution of consuming the versions file at (website) build time, remove the javascript code about it, and avoid the need to make a to the website repo on every new release.

Maybe we could get the best of both worlds! If we can keep track of the version number in the action in website, we could trigger a build and use that at build time, to update the version that way. And then remove the need for both JS and the commits. WDYT? I can do a little testing next week.
And do a separate dispatch to docs.

Not sure I follow that latest suggestion, but I still think it is cleaner to dispatch to the docs repo and persist the version number there – it already need to persist the updated docs and that repo is never updated manually by humans, in contrast to the website repo.
If we want to avoid the dispatch to the website repo, the website could perhaps fetch https://docs.lavinmq.com/latest_release.json with JavaScript? (there should be no rate limits on that)

There might be a point in persisting the version number in the website repo, it could be kinda nice have the bumps recorded in the git history along with all other changes. And maybe the docs repo shouldn't be "abused" to host the version file. Added some feedback in https://github.com/84codes/lavinmq-website/pull/48 that should help us get there.


console.log(publicRuntimeConfig) returns undefined in the client when you have '@zeit/next-css' installed along next.config.js

Bug report
I've installed @zeit/next-css for having a separate stylesheet in my project, however, when i try to apply env variables via publicRuntimeConfig, it returns undefined for me.
Describe the bug
This is a snippet of next.config.js
const withCSS = require('@zeit/next-css')

module.exports = {
  webpack: config => {
    config.node = {
      fs: 'empty'
    }

    return config
  }
}

module.exports = {
  publicRuntimeConfig: {
    API: 'test'
  }
}

module.exports = withCSS()

this is the error message i get after refreshing
./assets/css/nav.css 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
> .nav-item {
|   margin: 0 !important;
| }

To Reproduce
Steps to reproduce the behavior, please provide code snippets or a repository:

install package @zeit/next-css
create a test.css file and link it to client side.
add module.exports = withCSS() (if its not generated automatically after installing) in next.config.js
add module.exports = { publicRuntimeConfig: { API: 'test } } in next.config.js
console.log(publicRuntimeConfig) in page where css is linked (i assume you know how to setup getConfig() and all that gist)
run npm run dev and navigate to page

removing withCSS() and css link solves the issue. ive resorted to using

The issue is with how you're exporting your next.config. Your code is overriding the exported module each time you define module.exports = .... Plugins like next-css extends the object it wraps, so you can combine it all into one, like so:
module.exports = withCSS({
  webpack: config => {
    config.node = {
      fs: "empty"
    };
    return config;
  },
  publicRuntimeConfig: {
    API: "test"
  }
});

thank you ! that solves the problem.


Create transaction API's

Changes made in this Api

Created CRUD APIs for transaction /createTransaction /updateTransaction/:id /deleteTransaction/:id
Added API's for listing specific transaction and all transactions /getTransaction/:id /getTransactions /getTransactionsByUser/:userId

Hello @afaq-karim, I went through your pull request and made some changes as well. Looks nice! Good work. 🚀 🚀 🪙