Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Picker UI not being correctly redraw once initialValue property gets updated #26

Closed
wizche opened this issue Sep 9, 2018 · 17 comments · Fixed by #93
Closed

Picker UI not being correctly redraw once initialValue property gets updated #26

wizche opened this issue Sep 9, 2018 · 17 comments · Fixed by #93

Comments

@wizche
Copy link

wizche commented Sep 9, 2018

I'm experiencing the following issue:

  • In my model I set intialValue default to 1 (this property is mapped to the initialValue property of the picker widget)
  • In my widget builder method I call an async method that will load some data and update this initialValue property
  • I call notifyListeners/setState after the initialValue has been updated (async method finished)
  • the picker widget is redraw but the "current" value is still the old one, we see that the value has been changed because is not green anymore, meaning that it took the new value but it didn't displayed correctly (see screenshot below)

Without calling the async method, initialValue defaults to one (in green):
image

When calling the async method and the intialValue changed to 6, the UI doesn't reflect the new value:
image

Thanks

@MarcinusX MarcinusX added the bug label Sep 10, 2018
@MarcinusX
Copy link
Owner

Hey, could you provide source code so I can reproduce the bug?

@wizche
Copy link
Author

wizche commented Sep 10, 2018

@MarcinusX hi, I was just looking at the NumberPicker code, I saw there is an animateInt method that scrolls to the right position. Am I suppose to call it manually when the initialValue changes? If so, this would be more a feature request than a bug...

@wizche
Copy link
Author

wizche commented Sep 10, 2018

Here a simple example where the initialValue is updated to 10 once you press the button, the pickerUI doesn't reflect the value though.

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _initialValue = 5;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            NumberPicker.integer(
              initialValue: _initialValue,
              minValue: 0,
              maxValue: 100,
              step: 1,
              onChanged: (newValue) {},
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: () {
          setState(() {
            _initialValue = 10; // we update to 10, expect the picker to scroll to this value
          });
        },
        tooltip: 'Test',
        child: new Icon(Icons.add),
      ),
    );
  }
}

@MarcinusX
Copy link
Owner

Ok, so yeah, I think that calling animateInt is the way to go.

I cannot really imagine how I could animate to that changed value. I would have to run it on every build method asynchronously and I guess this is wouldn't be the cleanest approach.

I guess calling that method manually is not that bad, right?

@wizche
Copy link
Author

wizche commented Sep 10, 2018

I'm not very familiar yet with flutter/dart, how would you adapt my example to call animateInt when initialValue changes in a "clean" fashion?

Maybe whenn building the widget create a scrollcontroller with the right initialScrollOffset?

@MarcinusX
Copy link
Owner

It won't work like that because initialScrollOffset is initial. Meaning that it only matters when the widget is created, so it cannot be updated later on. (From what I know)

@wizche
Copy link
Author

wizche commented Sep 18, 2018

Maybe you want to keep this open as feature request? I'm probably not the only one that wants to set a value on the picker after an async operation (e.g. load last value from storage/API/...).

This is the (worst?) way I could think of for calling the animateInt(). I'm just calling a future on the builder method sometime in the future to be sure that the widget is "attached" already. How can I improve this?

new Future.delayed(new Duration(milliseconds: 100), () {
 try {
  picker.animateInt(model.value);
 } catch(e){
  print("Silently catching exception from picker ${e.toString()}"); // ScrollController not attached to any 
 scroll views
 }
});

@MarcinusX
Copy link
Owner

MarcinusX commented Sep 18, 2018

I don't see anything wrong about it. Why you don't like it?

@wizche
Copy link
Author

wizche commented Sep 18, 2018

because its a hack, it generates exception when the scrollcontroller is not attached, is non deterministic and even worst the code sucks 👎
I think in the long run this should be handled internally by the picker state. When a new initialValue is being set the widget is being redraw and so are the scrollOffset recalculated

@MarcinusX MarcinusX reopened this Sep 18, 2018
@MarcinusX
Copy link
Owner

Ok, I think you are right. :)
The only thing is that the main feature is actually picking a number by a user, not selecting it programmatically and my main concern is that this should not be disrupted by adding that option.

Would you consider proposing a solution in a pull request?

@wizche
Copy link
Author

wizche commented Sep 23, 2018

Since I'm not very experienced its probably better that someone else take this task :)

@praharshbhatt
Copy link

this issue still has not been solved?

@matteobertino
Copy link

Issue still present, this makes the widget unusable since there's no way for the user to select again the initialValue once changed.

@theflocco
Copy link

pls fix this.
Is it possible to get a list of currently rendered children, then iterate over them and call the function animateint?
Otherwise I don't know how to properly scroll to the changed value automatically

@tasibalint
Copy link

When is a fix comming, I understand it is a picker but when something on the widget changes and setState is called why is not the initial number set? or why cant we change the current value or something? is there a hack until this is resolved pretty annoying

@inforaudio
Copy link

I have the some problem.
In this ways the the widget cant no use.

@diaverde
Copy link

diaverde commented May 23, 2020

I had a similar issue. I solved it using the "animateInt" method, but without any need of a scrollcontroller.

Here is my code sample. I'm calling asynchronously an alert dialog, where I fetch a new value to be displayed by the Number Picker.

Note some key factors:

  • I'm using an async method, as in wizche's case.
  • I'm declaring the NumberPicker before using it, just after the start of the build method. In this way, I can use the "animateToInt" method later, as I do when I return from the async part.
  • _currentValue is not showed here, but I had defined it before, outside the build method.

I hope this can be useful to anyone with the same issue.

@override
  Widget build(BuildContext context) {

    NumberPicker pickerM = new NumberPicker.integer(
      itemExtent: 40,
      initialValue: _currentValueM,
      minValue: 0,
      maxValue: 10,
      onChanged: (newValue) => setState(() => _currentValueM = newValue)
    );    

    return AlertDialog(
      title: Text('Ingrese el número'),
      content: SingleChildScrollView(
        child: Column(children: [
          Row(
            children: <Widget>[
              pickerM,
            ],
          ),
      ),
      actions: <Widget>[
        FlatButton(
          child: Text('Cancelar'),
            onPressed: () {
              Navigator.of(context).pop();
            },
        ),
        FlatButton(
          child: Text('Ver recientes'),
          onPressed: () async {
            final redraw = await showDialog<bool>(
              context: context,                
              builder: (BuildContext context) {      
                  return ListTile(
                    title: Text(recentReports[i].horas.toString()),
                    onTap: () {
                      _currentValueM = recentReports[i].horas.floor();
                      Navigator.of(context).pop(true);
                    }                                  
                  );
                });
                return AlertDialog(
                  title: Text('Actividades recientes:'),
                  content: SingleChildScrollView(
                    child: Card(
                      child: Column(
                        children: listaAct,
                      ),
                    ),
                  ),
                );
              }
            );
            try {
              if (redraw != null) {
                if (redraw) setState(() {
                  pickerM.animateInt(_currentValueM);
                });
              }
            } catch (e) {
              print(e.toString());
            }     
          },
        ),
        FlatButton(
          child: Text('Aceptar'),
            onPressed: () {
              if ((_formKey.currentState.validate()) && (horasActNow != 0.0)) {
                // Other stuff
              }
            },
          ),
        ],
      )
    );
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants