How to disable click events for entire children except one selected child in flutter? - flutter

Scenario: I have number of children in Listview.builder. For every children there is button which invokes TTS(text to speech). I wanted when any one of child is pressed rest all children should be in Listview.builder un-clickable till pressed child finishes its TTS.
I got answer from google like absorb pointer, ignore pointer to solve this.
But i dont know how to implement above scenario using these widgets.

Store a bool in the State of the class enclosing the ListView.builder. This bool should store if one of the children is currently doing its TTS. If it is true you should set all of the onPressed(or equivalent) method to null to prevent other taps from triggering an action. Ex:
bool hasBeenClicked = false;
void yourTTSMethod() {
setState(() {
hasBeenClicked = true;
});
... //Do normal method body
setState(() {
hasBeenClicked = true;
});
}
//In build method with each `List` item:
GestureDetector(//Just for sample, use whatever click detector you're currently using
onTap: hasBeenClicked ? null : yourTTSMethod
)

Related

Change a dropdown's items when another dropdown's value is chosen in flutter (UI wont update)

I have two drop downs, and I want to do things when they get values selected. One of those is to change the second buttondrop items based on what's selected in the first dropdown.
For example:
Dropdown1 is a list of car manufactuers
Dropdown2 is a list of their models
Dropdown1 selects mercedes
Dropdown2 gets "E Class, S Class" etc
Dropdown1 selects lexus
Dropdown2 gets "ES, LS", etc
(Eventually the second drop down will update a listview as well, but haven't gotten to that yet.)
Data wise, it works, I update the list. The problem is the UI won't update unless I do a hot reload
Currently I am just having the dropdowns fetch their data and using Future builders
Future? data1;
Future? data2;
void initState(){
super.initState();
data1 = _data1AsyncMethod();
data2 = _data2AsyncMethod();
}
_data2AsyncMethod([int? item1_id]) async{
if(item1_id == null){
item2Classes = await DefaultItems().getAllItem2Classes();
listOfItem2ClassNames = DefaultItems().returnListOfItemClassNames(item2Classes);
}
else{
// The methods below calls the DefaultItems methods which have Futures all in them.
// The getAllItems calls a network file with GET methods of future type to get data and decodes them, etc.
// They build a list of the object type, ex List<Item2>
item2Classes = await DefaultItems().getAllItem2Classes(item1_id);
listOfItem2ClassNames = DefaultItems().returnListOfItemClassNames(item2Classes);
}
}
I have this Future Builder nested in some containers and paddings
FutureBuilder{
future: data2,
builder: (context, snapshot){
if(snapshot.connectionState != done...)
// return a circle progress indictator here
else{
return CustomDropDown{
hintText: 'example hint'
dropDownType: 'name'
dropDownList: listOfItem2ClassNames
dropDownCallback: whichDropDown,
}
The onChanged in CustomDropDown passes the dropDownType and the dropDownValue
The callback
whichDropDown(String dropDownType, String dropDownValue){
if(dropDownType == 'item1'){
//so if the first dropdown was used
// some code to get item_1's id and I call the data2 method
_data2AsyncMethod(item1_id);
}
Again the data updates (listOfItem2ClassNames) BUT the UI won't update unless I hot reload. I've even called just setState without any inputs to refresh but doesn't work
So how do I get the UI to update with the data, and is my solution too convoluted in the first place? How should I solve? StreamBuilders? I was having trouble using them.
Thanks
If you do a setState in the whichDropDown function, it will rebuild the UI. Although I'm not exactly sure what you want, your question is really ambiguous.
whichDropDown(String dropDownType, String dropDownValue){
if(dropDownType == 'item1'){
//so if the first dropdown was used
// some code to get item_1's id and I call the data2 method
_data2AsyncMethod(item1_id).then((_) {
setState(() {});
});
}
}
I notice a couple things:
nothing is causing the state to update, which is what causes a rebuild. Usually this is done explicitly with a call to setState()
in whichDropdown(), you call _data2AsyncMethod(item1_id), but that is returning a new Future, not updating data2, which means your FutureBuilder has no reason to update. Future's only go from un-completed to completed once, so once the Future in the FutureBuilder has been completed, there's no reason the widget will update again.
You may want to think about redesigning this widget a bit, perhaps rather than relying on FutureBuilder, instead call setState to react to the completion of the Futures (which can be done repeatedly, as opposed to how FutureBuilder works)

Edit object in edit screen and sync updated object in first screen (list of object screen)

I have a list of class user showing with StateNotifier in one screen. Clicking on any item, opens the edit screen of that user.
I have 2 questions to ask:
Should I send user object In another screen and edit the user or create state provider (without auto dispose) of single object and set it before opening edit screen and access it on another screen.
After edit the user, how to sync updated user object in user list screen (first screen).
Please feel free to ask any further detail.
Thank you in advance.
Should I send user object In another screen and edit the user or
create state provider (without auto dispose) of single object and set
it before opening edit screen and access it on another screen.
No, just send user object to edit screen, then after you edit done, pass data back to list screen by Navigator.pop(context, editedUser);
After edit the user, how to sync updated user object in user list
screen (first screen).
On list screen :
Navigator.pushNamed(context, 'yourEditScreen', arguments: editUser).then((value) {
if (value != null) {
//update your state list;
}
});
I won't cover how you send data between screens because it depends on what state manager you are using and there are actually many ways.
Assuming that you are on screen 1 after finishing screen 2, you have a list of old objects and a new object that has been modified. now you'll want to inject the changes into the old list object and call the update state
Example:
var objects = [{...}];
return ListView(
children: objects.map((object) {
final index = objects.indexOf(object);
onPressed() {
// handle will call when screen2 poped
handleEditedData(editedObject) {
// check object was edited and differences with old object (can override operator ==)
if (editedObject != null && editedObject != object) {
setState(() {
// replace old object by editedObject with the index
objects[index] = editedObject;
});
}
}
// push screen2 and wait to data from screen2 (using .pop with editedObj as parameter)
Navigator.of(context)
.pushNamed('screen2route', arguments: object)
.then(handleEditedData);
}
return ElevatedButton(onPressed: onPressed, child: const Text('edit'));
}).toList(),
);

Flutter. How to correctly update state of nested widgets?

I'm new in flutter and I'm trying to implement something like radio group with custom buttons in Android. For this I created StatefulWidget, which hold list of selectable buttons. For every button I was set press listener where I do something like this:
setState(() {
buttons.forEach((button) => button.isSelected = false);
buttons[selectedButtonIndex].isSelected = true;
});
And then my CustomButtonWidget changes color depending on the parameter isSelected .
All this works well. However, I have an additional requirement. I need my RadioGroupWidget to return the selected button type. For this I created a callback :
final ValueChanged<ButtonType> onChanged;
And now my button press listener looking like this:
onTap: () {
setState(() {
buttons.forEach((button) => button.isSelected = false);
buttons[selectedButtonIndex].isSelected = true;
});
onChanged(buttons[selectedButtonIndex].type);
}
Next I get this type of button in my other widget which is using RadioGroupWidget:
CustomRadioGroup(
onChanged: (value) {
setState(() {
buttonType= value;
});
}),
)
As you can see I call again setState. This is what leads to the problem. But I need to do this, because I need to update the state of another widget (for example let's call it InfoWidget) depending on the selected button.
After all these manipulations, the state of the InfoWidget is updated correctly, but the state of the selected button in the RadioGroupWidget does not change. I tried to debug this and I see that at first the parameter isSelected is set to true for the desired button, but after the state of the button I selected is not updated, because its parameter isSelected becomes false. And I don't understand why this is happening.
Please help, I am completely confused.

How best to make my scroll controllers available throughout the app?

Context: I'll be having a couple of scrollable lists in my app and I always want to scroll them to the latest item whenever an item is added.
Problem: My ListView.builders and the places where items are added are going to be quite far apart in my widget tree. Passing around all those scroll controllers via constructors seems to be super awkward.
My Solution:As I'm practising with Provider at the moment, I came up with a working solution using Provider:
class ScrollControllerProvider with ChangeNotifier {
ScrollController _paneController = ScrollController();
//setting up all other controllers here later
get paneController {
return _paneController;
}
void scrollHistory() {
WidgetsBinding.instance?.addPostFrameCallback((_) {
if (_paneController.hasClients) {
_paneController.jumpTo(_paneController.position.maxScrollExtent);
}
});
}
}
I'll add all scroll controllers to that provider and grab what I need, where I need it. It already works with one, but someone on reddit told me it's not a good idea, as scroll controllers should be disposed. Im not super knowledgeable on the topic of life cycle yet and find it difficult to assess this.
Questions: Is it really a bad idea to use Provider here? Can you help me to understand why? If yes, what is the best approach to solve this issue?
Provider is not the problem, using a disposable item inside a provider is. ScrollController is a disposable item related to its main Widget, or better to say its State.
If you want to notify your widgets about newly added items, create a variable inside the provider and listen to that variable in your widgets, then use your ScrollController to change the position.
To find out more about your question take a look at ScrollController class and Disposable class
For posterity, Payam Asefi pointed me in the right direction.
How I'm doing it now.
tldr; Provider contains a value that can be toggled and a method to toggle it. I provide the value where I can also access the scroll controler. If it is toggled, the scroll conroler is used. I provide the method to toggle the value where I add new items to the list.
item is added > value in provider is triggered > listeners realized the value has changed calling the build method > scroll controller is used to go to maxscrollextend.
Long answer with code:
Provider with a) a bool that can be toggled b) a method to toggle the bool c) a getter for the bool
Code:
class ScrollControllerToggles with ChangeNotifier {
bool _historyPaneSwitch = true;
get getTogglePaneSwitch {
return _historyPaneSwitch;
}
void toggleHistoryPane() {
_historyPaneSwitch = !_historyPaneSwitch;
notifyListeners();
}
}
In the widget I'm using the Listview.builder: a) I define a scroll controller, b) I use a function dependent on the _historyPaneSwitch inside that Provider. That funtion also uses the scroll controller to scroll the list to the end.
void triggerScrollController() {
bool scrollHistoryPane =
Provider.of<ScrollControllerToggles>(context).getTogglePaneSwitch;
WidgetsBinding.instance?.addPostFrameCallback((_) {
if (paneController.hasClients) {
paneController.jumpTo(paneController.position.maxScrollExtent);
}
});
}
In the widget adding new items to the list, I access the Provider again and grab the method to toggle "_historyPaneSwitch".
Function scrollHistoryPane =
Provider.of<ScrollControllerToggles>(context).toggleHistoryPane;
void dayChange(Function scrollHistoryPane) {
mainElementList.insert(0, MainElement(false, DateTime.now().toString()));
scrollHistoryPane;
}

How to add specific actions to ListView when user drags to its start or end (when the curvy blue lines appear)?

I'm curious is there an easy way to add such functionality to my Flutter ListViews?
For example, I would like to trigger some animation on another widget when the user drags "over the edge" on the ListView with all of it's items scrolled to the top or bottom, that is when the curvy blue line indicating that we're at the end of the list appears.
It obviously has an event firing up to show those lines anyway.
It's not hard actually, you can use a custom ScrollController with a listener:
declare it, then in initState put:
_myController = ScrollController();
_myController.addListener(_myScrollListener);
and the function itself can be something like this:
_myScrollListener(){
if (_myController.offset >= _controller.position.maxScrollExtent && !_myController.position.outOfRange) {
print("List end");
}
if (_myController.offset <= _controller.position.minScrollExtent && !_myController.position.outOfRange) {
print("List top");
}
Use the controller with your ListView, add what you need to the listener.