Programmatically focusing on InputField and opening up keyboard - dart

I have a situation where I need to programmatically focus on a InputField(such as in response to a button press).
I'm using the Focus.moveTo function; however, even though the InputField is focused (blinking cursor appears) the keyboard is not brought up.
It seems like the best solution would be to call the RequestKeyboard() function in _InputFieldState, but this is private.
What would be the best way to accomplish this?
Here is a code sample showing the workflow:
class InputFieldWrapper extends StatefulWidget {
#override
_InputFieldWrapperState createState() => new _InputFieldWrapperState();
}
class _InputFieldWrapperState extends State<InputFieldWrapper> {
InputValue _currentInput = new InputValue(text: 'hello');
// GlobalKey for the InputField so we can focus on it
GlobalKey<EditableTextState> _inputKey = new GlobalKey<EditableTextState>();
#override
Widget build(BuildContext context) {
return new Column(
children: [
// Button that should focus on the InputField when pressed
new IconButton(
icon: new Icon(Icons.message),
onPressed: () {
Focus.moveTo(_inputKey);
},
),
// InputField that should be focused when pressing the Button
new InputField(
value: _currentInput,
key: _inputKey,
onChanged: (InputValue input) {
setState(() {
_currentInput = input;
});
}
),
],
);
}
}

This was decided to be a bug and is being tracked at #7985.

Related

How to preserve state of Widgets with BottomNavigationBar in Flutter? [duplicate]

This question already has answers here:
How to preserve widget states in flutter, when navigating using BottomNavigationBar?
(9 answers)
Closed 3 years ago.
I have a BottomNavigationBar, specifically a BubbleBottomBar. I have nested MaterialApps to give a new Navigator to the inner Widgets. However, when I switch tabs each widget in the bottom navigation bar is rebuilt. This is not good for me, as I want to keep the widgets in the same state. How would I achieve this?
I think you can easily solve that problem with using CupertinoTabScaffold&CuppertinoTabBar&CupertinoTabView it has that feature.
read more about there if you needed: Cupertino Widgets
official example : Cupertino Navigation&TabBar
this is my code it works the way you want it to work.(not rebuilding when you change tabs) you can convert it to yours:
import 'package:flutter/cupertino.dart';
CupertinoTabScaffold(
tabBar: CupertinoTabBar(items: [
BottomNavigationBarItem(
icon: Icon(Icons.explore), title: Text('Explore')),
BottomNavigationBarItem(
icon: Icon(Icons.card_travel), title: Text('Adventure')),
BottomNavigationBarItem(
icon: Icon(Icons.search), title: Text('Search')),
BottomNavigationBarItem(
icon: Icon(Icons.collections_bookmark),
title: Text('Bookmarks')),
BottomNavigationBarItem(
icon: Icon(Icons.person), title: Text('Profile')),
]),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
switch (index) {
case 0:
return ExplorePage();
break;
case 1:
return AdventurePage();
break;
case 2:
return SearchTourPage();
break;
case 3:
return Text('Bookmark Page');
break;
case 4:
return ProfilePage();
break;
default:
return SearchTourPage();
}
},
);
})
You can use AutomaticKeepAliveClientMixin to force your bottom bar content to not be disposed. But for this thing to work you might have to wrap your BottomNavigationBar inside a Stateful Widget.
I think this question might have the deatiled answer that you're looking for.
Example:
class CustomBottomBar extends StatefulWidget {
#override
_CustomBottomBarState createState() => _CustomBottomBarState();
}
class _CustomBottomBarState extends State<CustomBottomBar> with AutomaticKeepAliveClientMixin {
#override
Widget build(BuildContext context) {
return BubbleBottomBar(
/*Your bottom bar code goes here*/
);
}
// Setting it true will force the bottom bar to never be disposed. This could be dangerous.
#override
bool get wantKeepAlive => true;
}

How to prevent changes in a dropdown in Flutter?

I tried to ask the user, if he is sure to change the data after pressing on a dropdown menu.
Currently, I launch an alert dialog after onChanged. Asking the user "Are you sure to change data ?". If Yes , I save the data changes. If "No", I close the alert dialog.
But if user chooses "No", the data has been changed and I don't want to change the data... How can I stop the change? I have a complicated solution where I save all data change, and when the user presses NO, I load the last data save before "NO" but I found this to complicated. Are there any other, more simple solution ? Thank you
Here is my code :
new DropdownButton<String>(
onChanged: (String changedValue) {
dialog(); //lanche dialog with choice of data modification
data=changedValue;
setState(() {
data;
});
},
value: data,
items: <String>['data1', 'data2', 'data3','data4', 'data5']
.map((String changedValue) {
return new DropdownMenuItem<String>(
value: changedValue,
child: new Text(changedValue),
);
}).toList()),
You should update the setState data value in your decision function. Check the following code.
new DropdownButton<String>(
onChanged: (String changedValue) {
_showDialog(changedValue); // I changed this.
},
value: data,
items: <String>['data1', 'data2', 'data3', 'data4', 'data5']
.map((String changedValue) {
return new DropdownMenuItem<String>(
value: changedValue,
child: new Text(changedValue),
);
}).toList()),
void _showDialog(selection) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: new Text("Alert Dialog title"),
content: new Text("Alert Dialog body"),
actions: <Widget>[
new FlatButton(
child: new Text("No"),
onPressed: () {
Navigator.of(context).pop();
},
),
new FlatButton(
child: new Text("Yes"),
onPressed: () {
// this is where data value updates.
setState(() {
data = selection;
});
Navigator.of(context).pop();
},
),
],
);
},
);
}
I know this is too late to answer this question since this question was asked almost 4 years back from now. But some one like me will come around this question I guess. Here is the answer for you guys.
We can just reset the value of a dropdown to its initial value by calling reset method on the key of the dropdown button's state key.
Declare a global key for DropDownButton widget in the state class of your widget or screen
final _dropDownKey = GlobalKey<FormFieldState>();
Attach the key in the constructor of your dropdown button
You can now call reset method from the current state of the dropdown key to reset the dropdown value to its initial value.
_dropDownKey.currentState?.reset();
in my case when the user taps "NO" in the confirmation dialog, I have to set the old value in the dropdown.
What I have done is I waited for the result from alert dialog by async/await (alert dialog returns a nullable bool). When the pop up is closed a boolean or null will be returned. I added a if statement to reset the dropdown value if alert dialog returns false or null (which means user tapped on "NO" button or user dismissed the dialog by tapping on the empty area).
Future<void> _showDropDownChangeConfirmAlert(BuildContext context, FBO? fbo){
......
final isChangeConfirmed = await AlertDialogUtils.showConfirmationDialog(
context,
title: title,
content: content,
onPrimaryActionPressed: () => widget.onFboSelected.call(fbo),
);
// If the user taps on No in the confirmation dialog
// Below code resets the dropdown value to the old selected FBO
if ((isChangeConfirmed ?? false) == false) {
if (mounted) {
_dropDownKey.currentState?.reset();
}
}
....
}
Ps: AlertDialogUtils is a class in my project. It won't works in your project. Copy the idea not the code :)

How to correctly call function from parent widget to child widget?

I'm trying to find / understand a simple flutter concept. I have a form that contains a date picker, the date picker is a stateful widget and opens the select date menu when tapped. I would like it to also open when the user taps "next" on the keyboard (to go to the next field, which is the date picker).
...
TextFormField(
...
textInputAction: TextInputAction.next,
focusNode: _focusNodeItem,
onFieldSubmitted: (value) {
_focusNodeItem.unfocus();
// TODO: open datePicker from here
},
...
),
DatePicker(
date: _reminder.expires,
selectDate: (date) {
setState(() {
_reminder =
_reminder.copy(expires: date.toString());
});
},
)
...
Datepicker widget is something like this:
class DatePicker extends StatefulWidget {
final String date;
final ValueChanged<DateTime> selectDate;
DatePicker({Key key, this.date, this.selectDate}) : super(key: key);
#override
_DatePickerState createState() => _DatePickerState();
}
class _DatePickerState extends State<DatePicker> {
...
void openDatePicker() {
showCupertinoModalPopup<void>(
context: context,
builder: (BuildContext context) {
return _buildBottomPicker(
CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
initialDateTime: date,
onDateTimeChanged: (DateTime newDateTime) {
setState(() {
date = newDateTime;
widget.selectDate(newDateTime);
});
},
),
);
},
);
}
...
}
I get that widgets should not "mess" with each other, but what about simple cases like this?
Is there a simple (and correct) way to call openDatePicker() from the parent widget?

Flutter show Progress HUD while doing background task

I am making movie maker app where i am showing progress hud when creating movie from list of videos.But Progress HUD progress indicator UI stuck at starting point. Following is my code:
class _MyHomePageState extends State<MyHomePage> {
static List<dynamic> videosPath = List();
static const MethodChannel methodChannel =
const MethodChannel('moviemaker.devunion.com/movie_maker_channel');
String _batteryLevel = 'Battery level: unknown.';
ProgressHUD _progressHUD;
bool _loading = false;
#override
void initState() {
super.initState();
// _getBatteryLevel();
_progressHUD = new ProgressHUD(
backgroundColor: Colors.black12,
color: Colors.white,
containerColor: Colors.blue,
borderRadius: 5.0,
text: 'Loading...',
loading: false,
);
}
#override
Widget build(BuildContext context) {
Widget child = new Scaffold(
appBar: new AppBar(
title: new Text('Movie Maker'),
actions: <Widget>[
// action button
IconButton(
icon: Icon(Icons.movie_creation),
onPressed: () {
_createMovieSync(videosPath);
},
)
],
),
body: Stack(children: [_progressHUD, _buildContentSection()]),
floatingActionButton: new FloatingActionButton(
onPressed: () {
debugPrint("Bipin - FAB pressed");
pickVideos().asStream().listen(_setResults);
},
tooltip: "Pick a Video",
child: new Icon(Icons.add),
),
);
return child;
}
void toggleProgressHUD() {
setState(() {
if (_loading) {
_progressHUD.state.dismiss();
} else {
_progressHUD.state.show();
}
_loading = !_loading;
});
}
void _createMovieSync(List<dynamic> paths) {
toggleProgressHUD();
_createMovie(videosPath).then((moviePath) {
debugPrint("Bipin - _createMovieSync Movie created path: $moviePath");
_startMovie(moviePath).then((_) {
debugPrint("Bipin - start movie then");
}).catchError((error) {
debugPrint("Bipin - start movie error:");
}).whenComplete(() {
debugPrint("Bipin - start movie completed.");
toggleProgressHUD();
});
}).catchError((error) {
debugPrint("Bipin - Create movie error: ${error.toString()}");
}).whenComplete(() {
debugPrint("Bipin - Create movie completed.");
});
}
Future<String> _createMovie(List<dynamic> paths) {
return Future<String>(() {
debugPrint("Bipin - Create movie future going to sleep");
sleep(Duration(seconds: 10));
debugPrint("Bipin - Create movie future wake up");
return "FilePath goes here";
});
// return methodChannel.invokeMethod('createMovie', {"videoPaths": paths});
}
Future<Null> _startMovie(String moviePath) async {
return Future<Null>(() {
debugPrint("Bipin - Start movie future going to sleep");
sleep(Duration(seconds: 10));
debugPrint("Bipin - Start movie future wake up");
});
// debugPrint("Bipin - Created Movie path: $moviePath");
// return methodChannel.invokeMethod('startMovie', {"moviePath": moviePath});
}
Here, I am starting movie creation when a user taps on Create Movie action button and showing progress HUD but it is not working as expected, It's showing just like here.
Updated code to use modal_progress_hud 0.0.6, As suggested in answer But it's still, showing hanging progress hud. Find code here: https://github.com/bipinvaylu/MovieMaker-Flutter/commit/43b317efc9fdbc6e67cf36b16e4350587bf517ae
I take a slightly different approach with the modal_progress_hud package which wraps the widget you wish to make modal (with a progress indicator). After the async call returns you turn off the progress indicator. You can then play the movie.
No matter which package you use, I would probably switch to another widget to play the movie triggered by a call to setState() after making a call to stop the progress indicator. Playing the movie before the original async call completes may be preventing the progress indicator from stopping. Food for thought.

Flutter - Trouble preserving focus between routes

I have a form that uses FocusNodes to visually indicate which part of the form is active. One field extends a PopupRoute as a kind of pop up 'keyboard'. My problem is, when I press that field, the keyboard pops up but the visual effect of the focus doesn't occur.
Some debugging from the FocusNode's listeners shows it gets focus but immediately loses it. I think it is because the new PopupRoute has a new FocusScopeNode, so my FocusNode doesn't have focus any more.
How can I keep the field focused while in the other route? I've tried:
Using FocusScope.of(context).reparentIfNeeded(focusNode) in all the build methods, which didn't do anything (I don't really understand this function tbh)
Passing the current FocusScope.of(context) into my custom PopupRoute to use. This actually worked, but after it's popped, I can't focus anything anymore (I guess it gets disposed?)
Code-wise, I'm calling requestFocus on the field tap, and adding this listener in initState:
widget.focusNode.addListener(() {
print(widget.focusNode);
if (widget.focusNode.hasFocus) {
Navigator.of(context).push(
CustomKeyboardPopupRoute(
state: widget.state,
position: //position stuff,
focusScopeNode: FocusScope.of(context), //the second of my ideas which didn't quite work above
)
).then((_) {
widget.focusNode.unfocus();
});
});
You are on the right track, indeed this happens because of the FocusScopeNode.
Make your keyboard route extend TransitionRoute:
class CustomKeyboardPopupRoute extends TransitionRoute {
#override
bool get opaque => false;
#override
Duration get transitionDuration => Duration(milliseconds: 300);
#override
Iterable<OverlayEntry> createOverlayEntries() sync* {
yield OverlayEntry(
opaque: false,
maintainState: true,
builder: _buildKeyboard,
);
}
Widget _buildKeyboard(BuildContext context) {
final positionAnimation = Tween(begin: Offset(0.0, 1.0), end: Offset.zero).animate(animation);
return SlideTransition(position: positionAnimation, child: Align(
alignment: Alignment.bottomCenter,
child: ...
),);
}
}
thanks very much.
Iterable<OverlayEntry> createOverlayEntries() sync* {
yield OverlayEntry(
opaque: false,
maintainState: true,
builder: (content) {
return ModalBarrier(dismissible: true);
}
);
yield OverlayEntry(
opaque: false,
maintainState: true,
builder: _buildContent,
);
}

Resources